Translate this page

Controlling The Real World With Computers
::. Control And Embedded Systems .::

Experiment 9 - Analog To Digital Conversion

Home

                                                       Order

Let me know what you think

Previous: Experiment 8 - Digital To Analog Converter
Next: Experiment 10 - Using Analog Inputs To Control Motors

The compliment to the digital to analog converter is the analog to digital converter. An analog to digital converter converts analog voltages to digital information that can be used by a computer.

It is useful to note that the digital data produced by an analog to digital converter is only approximately proportional to the analog input. That's because a perfect conversion is impossible due to the fact that digital information changes in steps, whereas analog is virtually continuous, at least down to the subatomic level.

A good example of the limitation is the converter on the board. It provides 8-bit numbers. That means it can produce the numbers 0 through 255. That's 256 numbers and 255 steps. There are simply not enough steps to represent all possible analog input values. The situation improves as the number of bits increases. A 20-bit converter gets a lot closer to real-world with 1,048,575 steps. It's important to remember however, that there are infinitely more analog values possible between a single step of any analog to digital converter than can be represented over the converter's entire span of digital values. Human-made will never match real-world.

One important element in analog to digital converters such as the ADC0809 on the board (see ADC0809.PDF) is the analog comparator. It looks like an op-amp schematically:

 


The difference is that it accepts analog inputs but produces a digital output. Its output will be high if the + analog level is greater than the - analog level, else its output will be low.

Another important component in the ADC0809 is a digital to analog converter (DA) similar to the one discussed in Experiment 8. The digital representation of the input can be determined by connecting the analog voltage input to be converted to the - input of the comparater, and the output of the DA to the + input:

 

The DA is initialized by storing only the high-order bit in it. That's bit 7 for the 8-bit converter on the board. If the comparator output is low, that means the test analog level from the DA is still lower than the input, so the bit is kept. If the comparator output is high, that means the test analog level from the DA is too high, so the bit is dropped. The other bits are tested in the same manner and either kept or dropped according to the status of the comparator. The process is called successive approximation, and is illustrated by the following flow chart:

 

Incidentally, a flow chart is a handy, easy way to describe a process before typing in the code. There are more geometric shapes in a full-blown flow chart, but diamonds for decisions and rectangles for actions can be used in most cases. One useful addition is the circle. Circles with letters in them are used to show transitions to other pages.

The flow chart above follows the written description of successive approximation. The DA is initialized by turning on bit 7. The process then moves to the decision diamond which checks the comparater. The bit being tested is turned off if the comparator output is high, else it's left on. The process then moves to another decision which asks if this is the last bit. If not, the next lower bit is turned on and control is passed to the top decision. The conversion ends after bit 0, the last bit, has been tested.

Consider the task of providing a number for an input of 3.21 volts. It will be assumed that the digital to analog converter provides a voltage and that the comparator compares voltages. An actual successive approximation converter could just as easily use current.

Recall from Experiment 8 that the output voltage of the digital to analog converter on the board is Vout = (5 * DAvalue)/256. This equation will be used to illustrate the process.

The flow chart says to start with bit 7 which has a weight of 128, then keep or drop bits according to the output of the comparator. The process used to convert 3.21 volts to a number is shown in the following table:

Test Bit

DA Binary Value

Decimal

(5 * DAvalue)/256

Comparison Result

10000000

10000000

128

2.5

low - keep bit

01000000

11000000

192

3.75

high - drop bit

00100000

10100000

160

3.125

low - keep bit

00010000

10110000

176

3.4375

high - drop bit

00001000

10101000

168

3.28125

high - drop bit

00000100

10100100

164

3.203125

low - keep bit

00000010

10100110

166

3.2421875

high - drop bit

00000001

10100101

165

3.22265625

high - drop bit

Three bits were kept, leaving 10100100 in the DA converter. The analog to digital converter will provide 164 as its numerical representation for 3.21 volts.

The ADC needs a clock to run. This is provided by dividing the 14.31818 MHz oscillator (OSC) signal on the ISA bus by 16 using a 74LS393 (see 74LS393.PDF). (Hz means Hertz, named after physicist, Gustav Ludwig Hertz. It means cycles per second, and MHz means megaHertz or millions of cycles per second.)

The 74LS393 is a dual, 4-bit ripple counter. A ripple counter uses a flip-flop as its basic element. The following is a simplified diagram of a single flip-flop. This one is called a D flip-flop:

 

The operation of a D flip-flop is very simple (see 74LS74.PDF for more detail on a typical D flip-flop).

If the data input (D) is high and a clock pulse is applied to the clock input, called clocking the flip-flop, the main Q output will go high and the inverted output (NOT Q) will go low. If the data input is low and the flip-flop is clocked, Q will go low and NOT Q will go high.

In other words, Q becomes the same state as the data input when the flip-flop is clocked, and NOT Q becomes the opposite state of the data input. Now notice what happens if NOT Q is tied to the data input:

 

This flip-flop clocks on the falling edge of the pulse (as does the 74LS393). That means the Q and NOT Q outputs change only on the falling edge of the input. One cycle of either the input or output is from falling edge to falling edge. Consider the following sequence of events:

Let's say NOT Q starts out high, making the data input high.

Q is always the opposite of NOT Q, so Q will be low.

The falling edge of the input clocks the flip-flop.

Q will become the same state as the data input. Thus, it will go high.

NOT Q is always the opposite of Q, so it will go low, making the data input low.

The next falling edge of the input clocks the flip-flop again.

Q will become the same state as the data input, thus Q will go low.

NOT Q will go high, along with data.

The output completes only a half cycle with each full cycle of the input. The result is a divide by two, or binary divide. Connect several of these stages together and the result is a counter that can divide an input pulse by any desired binary number (2, 4, 8, 16, etc). The flip-flop action will ripple through the stages.

A schematic from the data sheet of the 74LS393 is shown below. 1A and 2A are the clock inputs. They are triggered on the falling edge of the clock, as indicated by the bubble on their inputs. The clear inputs make all of the outputs low. There are 4 Q outputs for each group of 4 flip-flops. The sections are connected to each other on the board to provide 8 outputs on Header 4 as 8 different frequencies of pulsed wave forms. The ADC0809 analog to digital converter's clock input is connected to pins 6 and 13, where the stages are connected together and where the input is divided by 16.



Dividing 14.31818 MHz by 16 produces a clock of 894886.25Hz which permits about 11000 samples per second from the ADC0809, which is fast enough to remove the need for an anti-aliasing filter when recording signals up to about 5kHz. (Click here to see a definition of aliasing.) The portion of the schematic showing the 74LS393 clock section connected to the converter is shown below:

 

Two gates of the 74LS02 quad NOR gate (see 74LS02.PDF) in the above decode the read and write select for the ADC0809 analog to digital converter. One of the converter's 8 channels is selected using buffered address lines BA0, BA1 and BA2, which activates a switch array called a multiplexer. The Address Latch Enable (ALE) input on the ADC0809 must be high to cause the converter to lock in the selected channel for conversion:

 

Taking the START line high will start a conversion. ALE and START are connected together to simultaneously latch in the selected channel and start a conversion. A write port operation is used to select a channel and start a conversion. The ADC SELECT line from the 74LS138 will go low when the converter is selected. Note that when the converter is not selected, ADC SELECT will be high, forcing the outputs of both NOR gates low (see the Boolean logic section if you have forgotten the logic).

When both ADC SELECT and the BIOW (Buffered I/O Write - Active Low) lines are low, the output of the bottom NOR gate will go high. That will make the Address Latch Enable line high which will latch in the channel selected by BA0, BA1 and BA2. At the same time, the START line will go high, which will cause a conversion to start for the channel selected by the address lines.

The ADC0809 signals an End Of Conversion by taking its EOC line (pin 7) high. The EOC signal is available from the 74LS244 (see 74LS244.PDF), which is the same chip used in Experiment 1 to get the status of the three basic switch inputs:

 

The EOC signal can be read though the BD7 line on the 74LS244. Once the converter indicates data is available, the Output Enable (OE) line on the ADC0809 can be made high to cause the results of the conversion to be placed on the computer's data bus:

 

Output Enable is made high by making both ADC SELECT and BIOR (Buffered I/O Read - Active Low) low. This will cause the output of the upper NOR gate to go high, making OE high. Data will then be place on BD0 through BD7 and can be read by a program. The data will be for the channel selected when the conversion was started as described above.

To get a conversion from a channel, write anything to the base address plus the channel number, wait for the End Of Conversion line to go high, then read the converter. The channel numbers and locations, address offsets and start instructions for the analog to digital converter channels are:

To start a conversion on this channel

Which is ADC0809 pin number

And Header 1 input pin
(ground on 1 or 2)

Write anything to

Channel 0

26 = IN0

13 with JP1 removed, or through the preamp with JP1 in place

Base Plus 0

Channel 1

27 = IN1

12

Base Plus 1

Channel 2

28 = IN2

11

Base Plus 2

Channel 3

1 = IN3

10

Base Plus 3

Channel 4

2 = IN4

9

Base Plus 4

Channel 5

3 = IN5

8

Base Plus 5

Channel 6

4 = IN6

7

Base Plus 6

Channel 7

5 = IN7

6

Base Plus 7

What has been covered about the analog to digital converter so far can be used to automatically determine the base address being used for the board. Simply run through all of the possible base addresses, try to start a conversion for channel 0 of that base address, then look for the end of conversion signal. If the end of conversion signal is low then high, then there is a very good possibility that the correct channel number has been found. The following changes get_port() to an auto-detect function:

 
// find hardware port if one exists
unsigned get_port(void)
{
 int x;
 static unsigned local_port;
 unsigned int not_ready_count,ready_count;
 
 if(local_port == 32767)
 return 0;
 
 if(local_port > 0)
 return local_port;
 
 for(x=0x200; x<0x3c0; x+=0x40)   {     not_ready_count = 32767;     ready_count = 32767;      outp(x,0); /* start conversion */      while((inp(x+0x18) & 0x80) && --not_ready_count); /* wait for not ready */      while(!(inp(x+0x18) & 0x80) && --ready_count); /* wait for ready */      if(ready_count >< 32767 && ready_count > 0)
 {
 local_port = base = x;
 switch_port = base + 0x18;
 ppi_porta = base + 0x20;
 ppi_portb = base + 0x21;
 ppi_portc = base + 0x22;
 return base;
 }
 }
 
 local_port = 32767;
 return 0;
}

The static variable local_port is used to determine if a search has already been done. A new search is conducted if local_port is 0. A value of 32767 (a little less than the maximum positive integer value [short integer for 32 bit compilers]) indicates an attempt has already been made and a board could not be found, so 0 is returned to show there is no board.

The search runs through all but the highest possible setting on the DIP switch. The highest setting is not used since that location is used for ECG displays, and many modern displays still provide compatibility registers at that location. The x variable is used in a loop to run through all of the base addresses using the loop
for(x=0x200; x<0x3c0; x+=0x40)
Recall that there are 64 bytes between the base addresses; 0x40 = 64.

The outp(x,0) instruction attempts to start a conversion on channel 0 at the selected base address by writing a 0 to the location. The next line looks like this:
while((inp(x+0x18) & 0x80) && --not_ready_count);
This is a single-line loop that gets a port input from the base location to be tested (x) plus 0x18, which is the offset of the 74LS244. This information is ANDed with 0x80, which is the weight of bit 7, the location of EOC. If EOC is low, the loop will break because the left half of the decision returns false.

The right half of the decision checks to see if not_ready_count has decremented to zero. If a converter is at the address and has been started, EOC will initially go low. This is contrasted with port addresses with nothing on them which will generally return 0xff, indicating all of the bits are on. Thus, not_ready_count is set up with a count that times out if EOC never goes low. Its purpose is to cause a break out of the loop even if EOC remains high.

Notice the "--" in front of not_ready_count. This is a pre-decrement unary operator. In the past, we have used only the post-decrement operator, which has the "--" after the variable name. The same two forms can be used with "++" and other operators. Here, the variable is decremented first, then tested for 0. In the case of a post-decrement operator, the variable is tested then decremented.

Looking at the operation another way:

 
 stay the input from ANDed with AND the previously decremented
here as the port plus the weight | counter is still greater
long as 0x18 which is of bit 7 | than zero
 | the 74LS244 is high, | |
 | | | | |
while( (inp(x+0x18) & 0x80) && --not_ready_count);

The colon at the end of the line causes the loop to stay on the line until the EOC goes high or the counter counts down to zero. The counter is for protection. It's possible that the EOC line will go low then high again before the program gets to the while loop in a slow computer. After all, the converter can do its job in only about 1/11000 th second. It's also possible that a very fast computer could get to the loop line before EOC has had a chance to go low. If the loop is entered with EOC high, then the loop might run forever without the protection of the counter. It is advisable when writing bit detection loops to always have an escape. The above might not always be the best since the timeout depends on the speed of the computer, but it has worked on many computers thus far.

The next line waits for the EOC line to go high:

 
while(!(inp(x+0x18) & 0x80) && --ready_count); // wait for ready

The counter ready_count is used to judge how long EOC stays high once it turns on.

The most important difference between this line and the previous is that this line loops as long as EOC is low, as indicated by the exclamation mark, whereas the previous line looped while the line was high (neglecting the escape counters).

Both lines taken together cause the program to first wait until EOC goes low, then wait until it goes high again.

Notice what will happen to ready_count in the second line. The loop has timed out if ready_count decrements to zero. At the same time, since it takes a finite amount of time for EOC to go high, ready_count should be less than its original 32767. The ready_count variable should be less than 32767 but greater than 0 if the bit goes from low to high. Thus, the tested port number is kept and other variables set if the following condition is true:

if(ready_count < 32767 && ready_count > 0)

Click here to download digi9a.c
Click here to download digi9a.h
Click here to download outcont.h

Remember to save your copy first to protect it. The original get_port() in digital.c is renamed to oldget_port() just in case it's needed. Recompile digital.c to form an object module.

Use the following to test the new get_port(). Be sure to save whatport.c somewhere if you have downloaded it from Super Start (www.superstart.org):

 
 
// whatport.c
 
#include <stdio.h>
#include <conio.h>
#include <malloc.h>
#include <time.h>
#include <dos.h>
#include <stdlib.h>
#include <string.h>
 
extern unsigned get_port(void);
 
main()
{
 unsigned port = 0;
 
 port = get_port();
 
 if(port == 0)
 printf("no hardware found\n");
 
 else printf("port after get_port = %X\n",port);
}
 
// end whatport.c


Click here to download whatport.c. Be sure to save whatport.c somewhere if you have downloaded it from Super Start (www.superstart.org):

Compile whatport.c then link it with the digital object module to form whatport.exe. Type whatport <enter> to run it. Try turning on different combinations of DIP switches then running whatport to confirm that the new settings are properly detected. It would be a good idea not to turn off all three switches so as not to interfere with video cards with ECG compatibility.

Part of the table from the hardware page is repeated below for reference. Notice the 0x40 step size:

Switch Settings

 

1 2 3

Base Address

1 1 1

200

 

0 1 1

240

 

1 0 1

280

 

0 0 1

2C0

 

1 1 0

300

 

0 1 0

340

 

1 0 0

380

 

0 0 0

3C0 (not tested)

 

Getting analog data is easy once the port is known. Just write anything to the port plus the channel number, wait for EOC to go high, then read the port number (not plus the channel). The following continuously reads channel 0:

 
 
// experi9a.c
 
#include <stdio.h>
#include <conio.h>
#include <malloc.h>
#include <time.h>
#include <dos.h>
#include <stdlib.h>
#include <string.h>
 
extern unsigned get_port(void);
 
main()
{
 unsigned port;
 int x;
 
 port = get_port();
 
 if(port == 0)
 {
 printf("no hardware found\n");
 return;
 }
 
 printf("port after get_port = %X\n",port);
 
 while(!kbhit())
 {
 outp(port, 0); // start channel 0 conversion
 while(!(inp(port+0x18) & 0x80)); // wait for ready
 x = inp(port);
 printf("%4d",x);
 }
}
 
// experi9a.c

Click here to download experi9a.c.
Click here to download digi9a.c
Click here to download digi9a.h
Click here to download outcont.h

Compile experi9a.c and digi9a to make object files then link experi9a with digi9a to make experi9a.exe. To run it, just type in experi9a <enter>.

The values from the analog to digital converter's channel 0 will print on the screen until a key is pressed. Try turning the multi-turn offset trimmer. You are adjusting the offset of the microphone preamp and should see the numbers change. If they don't change, make sure JP1 is in place. Adjust the trimmer so the program reads about 127 or 128. That will put the preamp offset at about 2.5 volts, or about half of the converter's 5 volt range.

 

Plug in a mike and talk into it. If you talk loud enough, you should see the numbers range from 0 to 255. The rate at which the converter is accessed is not fast enough to accurately record a voice, but some of the changing values can be seen. The rate is slowed most by the printing. It takes a long time to print - much longer than can be spared if we want to record something with frequencies as high as the human voice (about 3kHz).

The following program is capable of recording and playing back voice. The key is to remove everything that needlessly takes up time. Notice, for example, that the EOC location in the above is referenced by the inp(port+0x18) instruction. The problem is that the location is re-caluculated each iteration of the loop, even though it is a constant value. To save processing time, pre-calculate all constant values used in a loop. The port+0x18 term is placed into the eoc variable in the following program. It is calculated only once rather than with each iteration of a loop.

All that is done in the record operation is to record. There is no printing or playing back of data. A block of data is recorded, then the same block is played back. The data[] array is used to hold the information. It is first loaded with information from the analog to digital converter in a manner similar to experi9a. Interrupts are first disabled so they will not interfere. A conversion is started on channel 0 for each of 30000 bytes. A data byte is obtained from the converter when a high EOC is detected and placed in the appropriate array position.

Interrupts are enabled again after all 30000 bytes have been recorded, but only long enough to indicate that playback is about to begin. Playback looks a lot like record. A conversion is started on channel 0 and a high EOC is waited for. This dummy conversion is performed in order to provide playback with about the same timing as record. The appropriate data byte from the array is sent to the digital to analog converter when a high EOC is detected.

The program will first record then play back about 3 seconds of data. Pressing any key will terminate the program, although it can take up to 6 seconds since the program must first enable interrupts before the computer can handle keyboard input.

 
 
// experi9b.c
 
#include <stdio.h>
#include <conio.h>
#include <malloc.h>
#include <time.h>
#include <dos.h>
#include <stdlib.h>
#include <string.h>
 
extern unsigned get_port(void);
 
main()
{
 unsigned ADC_Chan0,dac1,eoc;
 int count;
 char data[30000];
 
 ADC_Chan0 = get_port();
 
 if(ADC_Chan0 == 0)
 {
 printf("no hardware found\n");
 return;
 }
 
 dac1 = ADC_Chan0 + 8;
 eoc = ADC_Chan0 + 0x18;
 
 printf("ADC Channel 0 after get_port = %X\n",ADC_Chan0);
 
 while(!kbhit())
 {
 puts("Recording");
 disable();
 for(count=0; count<30000; count++)
 {
 outp(ADC_Chan0, 0); // start channel 0 conversion
 while(!(inp(eoc) & 0x80)); // wait for ready
 data[count] = (char)inp(ADC_Chan0);
 }
 enable();
 
 puts("Playing");
 disable();
 for(count=0; count<30000; count++)
 {
 outp(ADC_Chan0, 0); // start channel 0 conversion
 while(!(inp(eoc) & 0x80)); // wait for ready
 outp(dac1, data[count]); // put the data in the DAC
 }
 enable();
 
// for(count=0; count<30000; count++)
// printf("%4d",data[count]);
 }
}
 
// experi9b.c

Click here to download experi9b.c.

Compile experi9b then link the object module with the digi9a object module. Type in experi9b <enter> to run the program. Speak into the microphone when the program says it's recording, and listen when it says it's playing back. You should hear pretty good quality as long as you don't speak too softly or too loudly.

Many industrial applications need AD sample rates that are only a fraction of the speed required for recording voice. Analog information that exhibits a maximum rate of 10 cycles per second for example, will be captured pretty well at a 100Hz sample rate.

Capturing analog information in the timer interrupt service routine provides for a known sample rate and automates the process. To accomplish this, we will start out with an array of structures and an enumeration that will be added to the timer header to make timer9c.h. There are also some prototypes that will be described shortly:

 
 
// DA structures, prototypes & variables
unsigned eoc;
extern unsigned base;
int DA_Enabled = 0;
int CurrentChannel = 0;
 
struct anachan
{
 int data;
 int status;
} AnnalogChannel[8];
 
enum anastat
{
 INACTIVE,
 START_CONVERSION,
 DATA_READY
};
 
unsigned InitializeAnalog(void);
int TurnOnAnalog(int channel);
int TurnOffAnalog(int channel);
int GetChannelValue(int channel);

Click here to download timer9c.h

The structures contain a data element to hold the results of the conversion. It also has a status element to show if the channel is inactive, ready to have a conversion started for it, or has data ready to be loaded. The prototypes describe four routines:

InitializeAnalog() checks to see if there is hardware by calling get_port() then memsets the array to 0 and sets a channel counter to 0.
TurnOnAnalog() turns on a channel by setting the status element of the channel to START_CONVERSION. It also turns on a global indicator showing the AD is active.
TurnOffAnalog() turns a channel off by setting the status element of the channel to INACTIVE. It also runs through the array and turns off the global indicator if it finds no active channels.
GetChannelValue() obtains the results of a channel's conversion

Here's what the routines look like in timer9c.c:

 
// ======================= DA Routines ======================= 
 
unsigned InitializeAnalog(void)
{
 base = get_port();
 eoc = base + 0x18;
 
 if(!base)
 return 0;
 
 memset(&AnnalogChannel, 0, sizeof(AnnalogChannel));
 
 CurrentChannel = 0;
 
 return base;
}
 
int TurnOnAnalog(int channel)
{
 if(channel < 0 || channel > 7)
 return -1;
 
 AnnalogChannel[channel].status = START_CONVERSION;
 
 DA_Enabled = 1;
 
 return channel;
}
 
int TurnOffAnalog(int channel)
{
 int x;
 
 if(channel < 0 || channel > 7)
 return -1;
 
 AnnalogChannel[channel].status = INACTIVE;
 
 for(x=0; x<8; x++)   {     if(AnnalogChannel[channel].status != INACTIVE)       break;   }    if(x == 8)  // all channels are inactive     DA_Enabled = 0;    return channel; }  int GetChannelValue(int channel) {   if(channel >< 0 || channel > 7)
 return -1;
 
 if(AnnalogChannel[channel].status == INACTIVE)
 return -1;
 
 return AnnalogChannel[channel].data;
 
 return channel;
}

The timer interrupt service routine has been modified to handle AD conversions as well as pwm digital outputs:

 
// the timer interrupt handler
// pwm type:
// 0 = unidirctional, no brake
// 1 = unidirectional with brake
// 2 = pwm line, directional line, no brake
// 3 = pwm line, directional line, with brake
// 4 = dual pwm lines -- both high = brake
// 5 = pwm line and two direction lines as for L298
// 255 = last slot -- leave
interrupt new_timer()
{
 int x;
 
 disable();
 
 timer_counter++;
 
 if(DA_Enabled) // is DA section enabled?
 {
 // look for start conversion or data ready status
 while(!AnnalogChannel[CurrentChannel].status)
 {
 CurrentChannel++;
 if(CurrentChannel > 7)
 CurrentChannel = 0;
 }
 
 switch(AnnalogChannel[CurrentChannel].status)
 {
 case START_CONVERSION:
 // will be ready at next interrupt, so say so
 AnnalogChannel[CurrentChannel].status = DATA_READY;
 break;
 
 case DATA_READY:
 // check eoc even though it's probably already ready
 while(!(inp(eoc) & 0x80));
 
 // load data into structure
 AnnalogChannel[CurrentChannel].data = inp(base);
 
 // set up for next
 AnnalogChannel[CurrentChannel].status = START_CONVERSION;
 
 CurrentChannel++; // bump up to next channel
 if(CurrentChannel > 7)
 CurrentChannel = 0;
 
 // this will find a channel that needs
 // a start even if it's just the above
 while(AnnalogChannel[CurrentChannel].status != START_CONVERSION)
 {
 CurrentChannel++; // get away from above channel
 if(CurrentChannel > 7)
 CurrentChannel = 0;
 }
 
 break;
 
 } // end switch(AnnalogChannel[CurrentChannel].status)
 
 // start a conversion - either the one designated
 // or the next one found after a data load
 outp(base + CurrentChannel, 0);
 
 } // end if(DA_Enabled)
 
 if(OutputControlActive)
 {
............. rest of new_timer() continues .............

Click here to download timer9c.c

You might notice that the pwm control section also has a global active indicator. The analog section first checks DA_Enabled, the global active indicator for analog, to make sure it has been set. No channels have been turned on if it is not.

The first thing done after that is to find the first active channel. The enumeration lists INACTIVE first, so it's 0. The other two possibilities are 1 or 2, so CurrentChannel is bumped up until a non- zero is found for a status element. CurrentChannel is wrapped back around to 0 if it goes over 7.

A switch statement then looks for a status of START_CONVERSION or DATA_READY. In the first instance, the status is simply changed to DATA_READY. That can be done since it will be ready by the time another interrupt occurs. If the status is already DATA_READY, the data element for the channel is loaded with the results of the conversion. The channel is then given a status of START_CONVERSION and a search is made for the next channel that needs a conversion started. It will be the one just set if only one channel is active.

A conversion is started following the switch statement. If a START_CONVERSION was found in the switch statement, that channel will be started. If a DATA_READY was found in the switch statement, then the next channel with a status of START_CONVERSION will be started.

The following sets up channel 0 for a sampling rate of 100Hz then prints the data obtained util a key is pressed. Vary the offset trimmer and talk in the microphone to see the effect:

 
 
// experi9c.c
 
#include <dos.h>
#include <stdio.h>
#include <bios.h>
 
// defines OC structure
#include "outcont.h" 
 
// include header with constants
#include "constant.h"
 
// include header with external prototypes
#include "extern.h"
 
void main(void)
{
 int x;
 double dc,oldfreq,newfreq;
 
 oldfreq = 1193180.0/65536.0;
 
 set_up_new_timer(100.0);
 
 newfreq = get_frequency();
 
 printf("old frequency = %f new frequency = %fHz\n"
 ,oldfreq,newfreq);
 
 x = (int)InitializeAnalog();
 
 printf("init ana = %X\n",x);
 
 x = TurnOnAnalog(0);
 printf("TurnOnAnalog(0); = %d\n",x);
 while(!kbhit())
 {
 x = GetChannelValue(0);
 printf("%4d",x);
 }
 
 // be sure to restore the timer!
 restore_old_timer();
}
 
// end experi9c.c

Click here to download experi9c.c
Click here to download constant.h
Click here to download outcont.h
Click here to download extern.h

This following will set up then print all eight channels:

 
 
// experi9d.c
 
#include <dos.h>
#include <stdio.h>
#include <bios.h>
 
// defines OC structure
#include "outcont.h" 
 
// include header with constants
#include "constant.h"
 
// include header with external prototypes
#include "extern.h"
 
void main(void)
{
 int x;
 double dc,oldfreq,newfreq;
 
 oldfreq = 1193180.0/65536.0;
 
 set_up_new_timer(800.0);
 
 newfreq = get_frequency();
 
 printf("old frequency = %f new frequency = %fHz\n"
 ,oldfreq,newfreq);
 
 x = (int)InitializeAnalog();
 
 printf("init ana = %X\n",x);
 for(x=0; x<8; x++)
 printf("TurnOnAnalog(%d) = %d\n",x,TurnOnAnalog(x));
 printf("Press any key to continue. ");
 getch();
 puts(" ");
 
 while(!kbhit())
 {
 for(x=0; x<7; x++)
 printf("%4d",GetChannelValue(x));
 printf("%4d\n",GetChannelValue(x));
 }
 
 // be sure to restore the timer!
 restore_old_timer();
}
 
// end experi9d.c

Click here to download experi9d.c.

Notice that the timer is set up for 800Hz. Since a single channel is sampled with each timer interrupt, 800 interrupts per second are needed to provide 100 samples per second for eight channels. Determine how many samples per second are needed per channel then multiply that by the number of channels to be set up to get the timer frequency. It's probably a good idea not to go over about 5000Hz though. A person could get 625 samples per second for each of the eight channels with 5000Hz.

To test the program, connect pins 6, 7, 8, 9, 10, 11, 12 and 13 together on Header 1 using the 16-conductor ribbon cable supplied with Experimenter's Kit #1 (see the order page). Vary the offset control and the digital values should track each other pretty well. Speak in the microphone and all 8 channels should show a response since they are all connected to the preamp output.

Previous: Experiment 8 - Digital To Analog Converter
Next: Experiment 10 - Using Analog Inputs To Control Motors

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