## 21. Data Aquisition

### 21.1 Introduction

An analog value is continuous, not discrete, as shown in figure 17.1. In the previous chapters, techniques were discussed for designing logical control systems that had inputs and outputs that could only be on or off. These systems are less common than the logical control systems, but they are very important. In this chapter we will examine analog inputs and outputs so that we may design continuous control systems in a later chapter.

Figure 17.1 - Logical and Continuous Values

Typical analog inputs and outputs for PLCs are listed below. Actuators and sensors that can be used with analog inputs and outputs will be discussed in later chapters.

Inputs:

• oven temperature

• fluid pressure

• fluid flow rate

Outputs:

• fluid valve position

• motor position

• motor velocity

This chapter will focus on the general principles behind digital-to-analog (D/A) and analog-to-digital (A/D) conversion. The chapter will show how to output and input analog values with a PLC.

To input an analog voltage (into a PLC or any other computer) the continuous voltage value must be ’sampled’ and then converted to a numerical value by an A/D converter. Figure 17.2 shows a continuous voltage changing over time. There are three samples shown on the figure. The process of sampling the data is not instantaneous, so each sample has a start and stop time. The time required to acquire the sample is called the ’sampling time’. A/D converters can only acquire a limited number of samples per second. The time between samples is called the sampling period ’T’, and the inverse of the sampling period is the sampling frequency (also called sampling rate). The sampling time is often much smaller than the sampling period. The sampling frequency is specified when buying hardware, but for a PLC a maximum sampling rate might be 20Hz.

Figure 17.2 - Sampling an Analog Voltage

A more realistic drawing of sampled data is shown in Figure 17.3. This data is noisier, and even between the start and end of the data sample there is a significant change in the voltage value. The data value sampled will be somewhere between the voltage at the start and end of the sample. The maximum (Vmax) and minimum (Vmin) voltages are a function of the control hardware. These are often specified when purchasing hardware, but reasonable ranges are;

0V to 5V

0V to 10V

-5V to 5V

-10V to 10V

The number of bits of the A/D converter is the number of bits in the result word. If the A/D converter is ’8 bit’ then the result can read up to 256 different voltage levels. Most A/D converters have 12 bits, 16 bit converters are used for precision measurements.

Figure 17.3 - Parameters for an A/D Conversion

The parameters defined in Figure 17.3 can be used to calculate values for A/D converters. These equations are summarized in Figure 17.4. Equation 17.1 relates the number of bits of an A/D converter to the resolution. Equation 17.2 gives the error that can be expected with an A/D converter given the range between the minimum and maximum voltages, and the resolution (this is commonly called the quantization error). Equation 17.3 relates the voltage range and resolution to the voltage input to estimate the integer that the A/D converter will record. Finally, equation 17.4 allows a conversion between the integer value from the A/D converter, and a voltage in the computer.

Figure 17.4 - A/D Converter Equations

Consider a simple example, a 10 bit A/D converter can read voltages between -10V and 10V. This gives a resolution of 1024, where 0 is -10V and 1023 is +10V. Because there are only 1024 steps there is a maximum error of ±9.8mV. If a voltage of 4.564V is input into the PLC, the A/D converter converts the voltage to an integer value of 746. When we convert this back to a voltage the result is 4.570V. The resulting quantization error is 4.570V-4.564V=+0.006V. This error can be reduced by selecting an A/D converter with more bits. Each bit halves the quantization error.

Figure 17.5 - Sample Calculation of A/D Values

If the voltage being sampled is changing too fast we may get false readings, as shown in Figure 17.6. In the upper graph the waveform completes seven cycles, and 9 samples are taken. The bottom graph plots out the values read. The sampling frequency was too low, so the signal read appears to be different that it actually is, this is called aliasing.

Figure 17.6 - Low Sampling Frequencies Cause Aliasing

The Nyquist criterion specifies that sampling frequencies should be at least twice the frequency of the signal being measured, otherwise aliasing will occur. The example in Figure 17.6 violated this principle, so the signal was aliased. If this happens in real applications the process will appear to operate erratically. In practice the sample frequency should be 4 or more times faster than the system frequency.

There are other practical details that should be considered when designing applications with analog inputs;

• Noise - Since the sampling window for a signal is short, noise will have added effect on the signal read. For example, a momentary voltage spike might result in a higher than normal reading. Shielded data cables are commonly used to reduce the noise levels.

• Delay - When the sample is requested, a short period of time passes before the final sample value is obtained.

• Multiplexing - Most analog input cards allow multiple inputs. These may share the A/D converter using a technique called multiplexing. If there are 4 channels using an A/D converter with a maximum sampling rate of 100Hz, the maximum sampling rate per channel is 25Hz.

• Signal Conditioners - Signal conditioners are used to amplify, or filter signals coming from transducers, before they are read by the A/D converter.

• Resistance - A/D converters normally have high input impedance (resistance), so they affect circuits they are measuring.

• Single Ended Inputs - Voltage inputs to a PLC can use a single common for multiple inputs, these types of inputs are called ’single’ ended inputs. These tend to be more prone to noise.

• Double Ended Inputs - Each double ended input has its own common. This reduces problems with electrical noise, but also tends to reduce the number of inputs by half.

Figure 17.7 - A Successive Approximation A/D Converter

### 21.3 Analog Outputs

Analog outputs are much simpler than analog inputs. To set an analog output an integer is converted to a voltage. This process is very fast, and does not experience the timing problems with analog inputs. But, analog outputs are subject to quantization errors. Figure 17.11 gives a summary of the important relationships. These relationships are almost identical to those of the A/D converter.

Figure 17.11 - Analog Output Relationships

Assume we are using an 8 bit D/A converter that outputs values between 0V and 10V. We have a resolution of 256, where 0 results in an output of 0V and 255 results in 10V. The quantization error will be 20mV. If we want to output a voltage of 6.234V, we would specify an output integer of 160, this would result in an output voltage of 6.250V. The quantization error would be 6.250V-6.234V=0.016V.

The current output from a D/A converter is normally limited to a small value, typically less than 20mA. This is enough for instrumentation, but for high current loads, such as motors, a current amplifier is needed. This type of interface will be discussed later. If the current limit is exceeded for 5V output, the voltage will decrease (so don’t exceed the rated voltage). If the current limit is exceeded for long periods of time the D/A output may be damaged.

Figure 17.12 - A Digital-To-Analog Converter

### 21.4 Real-Time Processing

Any computer running a process should use a real-time operating system. The purpose of a real-time operating system is primarily to ensure that a process runs within a specified time interval, normally a small fraction of a system. This capability is often not a common part of most operating systems, but it is relatively easy to add. When it is not a real-time process, it common for another process to monopolize the processor and cause erratic delays. When this happens the control program may not respond to a control event for a second or more. This would generally be a bad thing in a time critical system.

- need to be able to specify how often a process runs.

- RTLinux

- system clock for slower processes.

### 21.7 Accessing DAQ Cards form Linux

Listing 16.1 - DAS08 Driver Header File (das08_io.h)

#include "../include/global.h"

#ifndef _DAS08__

#define _DAS08__

#define CARDBASE 0x300

#define ADCHIGH 0 // AD Data Registers

/* A/D Status and Control Register */

/* Auxiliary port on analog bus */

#define PORTAUX 2

/* Programmable Gain Register */

#define GAIN 3

/* Counter Load & Read Registers */

#define CCONFIGPORT 7 // Counter Control Register

/* D/A 0 Control Registers */

#define DAC0LOW 8

#define DAC0HIGH 9

/* D/A 1 Control Registers */

#define DAC1LOW 10

#define DAC1HIGH 11

/* 82C55 Digital I/O Registers */

#define PORTA 12

#define PORTB 13

#define PORTC 14

#define PORTCL 12345 /* real port is 0x30e bits 0-3 */

#define PORTCH 6789 /* real port is 0x30e bits 4-7 */

/* 82C55 Control Register */

#define DCONFIGPORT 15

#define DIGITALOUT 1

#define DIGITALIN 2

#define HIGHONLASTCOUNT 0

#define ONESHOT 1

#define RATEGENERATOR 2

#define SQUAREWAVE 3

#define SOFTWARESTROBE 4

#define HARDWARESTROBE 5

/* Range Codes */

#define BIP10VOLTS 0x08

#define BIP5VOLTS 0x00

#define BIP2PT5VOLTS 0x02

#define BIP1PT25VOLTS 0x04

#define BIPPT625VOLTS 0x06

#define UNI10VOLTS 0x01

#define UNI5VOLTS 0x03

#define UNI2PT5VOLTS 0x05

#define UNI1PT25VOLTS 0x07

class das08{

protected:

public:

int base; // card setup information

int chan0;

int chan1;

int portA; // port data directions

int portB;

int portCL;

int portCH;

int *data_portA; // hooks to global values

int *data_portB;

int *data_portCL;

int *data_portCH;

int *data_portXI;

int *data_portXO;

int *data_AI0;

int *data_AI1;

int *data_AI2;

int *data_AI3;

int *data_AI4;

int *data_AI5;

int *data_AI6;

int *data_AI7;

int *data_AO0;

int *data_AO1;

das08();

~das08();

int configure(char*);

int connect();

int scan();

int disconnect();

int DConfigPort(int, int);

int DIn(int, int*);

int DBitIn(int, int, int*);

int DOut(int, int);

int DBitOut(int, int, int);

int C8254Config(int, int);

int CIn(int, int*);

int AIn(int, int*);

int AOut(int, int);

};

#endif

Listing 16.2 - DAS08 Driver File (das08_io.cpp)

#include <errno.h>

#include <signal.h>

#include <stdio.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <sys/wait.h>

#include <netinet/in.h>

#include <netdb.h>

#include <sys/time.h>

#include <fcntl.h>

#include <stdlib.h>

#include <unistd.h>

#include <string.h>

#include <unistd.h>

#include <sys/io.h>

#include "das08_io.h"

#include "../include/process.h"

int bits[]={0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};

das08::das08(){

base = CARDBASE; // default cardbase

chan0 = BIP10VOLTS; // default AD ranges

chan1 = BIP10VOLTS;

portA = DIGITALIN;

portB = DIGITALIN;

portCH = DIGITALIN;

portCL = DIGITALIN;

}

das08::~das08(){

}

int das08::configure(char *file_name){

int error;

FILE *fp_in;

char params[200];

error = NO_ERROR;

if((fp_in = fopen(file_name, "r")) != NULL){

fgets(params, 200, fp_in);

while(feof(fp_in) == 0){

if((params[0] != ’#’) && (strlen(params) > 3)){

if(params[0] == ’B’){

base = atoi(&(params[1]));

} else if(strncmp("A0", params, 2) == 0){

if(strncmp("BIP10VOLTS", &(params[2]), 10) == 0){ chan0 = BIP10VOLTS;

} else if(strncmp("BIP5VOLTS", &(params[2]), 9) == 0){ chan0 = BIP5VOLTS;

} else if(strncmp("BIP2PT5VOLTS", &(params[2]), 12) == 0){ chan0 = BIP2PT5VOLTS;

} else if(strncmp("BIP1PT25VOLTS", &(params[2]), 13) == 0){ chan0 = BIP1PT25VOLTS;

} else if(strncmp("BIPPT625VOLTS", &(params[2]), 13) == 0){ chan0 = BIPPT625VOLTS;

} else if(strncmp("UNI10VOLTS", &(params[2]), 10) == 0){ chan0 = UNI10VOLTS;

} else if(strncmp("UNI5VOLTS", &(params[2]), 9) == 0){ chan0 = UNI5VOLTS;

} else if(strncmp("UNI2PT5VOLTS", &(params[2]), 12) == 0){ chan0 = UNI2PT5VOLTS;

} else if(strncmp("UNI1PT25VOLTS", &(params[2]), 13) == 0){ chan0 = UNI1PT25VOLTS;

} else {

error_log(MINOR, "Unrecognized DAS08 analog A0 output range");

error = ERROR;

}

} else if(strncmp("A1", params, 2) == 0){

if(strncmp("BIP10VOLTS", &(params[2]), 10) == 0){ chan1 = BIP10VOLTS;

} else if(strncmp("BIP5VOLTS", &(params[2]), 9) == 0){ chan1 = BIP5VOLTS;

} else if(strncmp("BIP2PT5VOLTS", &(params[2]), 12) == 0){ chan1 = BIP2PT5VOLTS;

} else if(strncmp("BIP1PT25VOLTS", &(params[2]), 13) == 0){ chan1 = BIP1PT25VOLTS;

} else if(strncmp("BIPPT625VOLTS", &(params[2]), 13) == 0){ chan1 = BIPPT625VOLTS;

} else if(strncmp("UNI10VOLTS", &(params[2]), 10) == 0){ chan1 = UNI10VOLTS;

} else if(strncmp("UNI5VOLTS", &(params[2]), 9) == 0){ chan1 = UNI5VOLTS;

} else if(strncmp("UNI2PT5VOLTS", &(params[2]), 12) == 0){ chan1 = UNI2PT5VOLTS;

} else if(strncmp("UNI1PT25VOLTS", &(params[2]), 13) == 0){ chan1 = UNI1PT25VOLTS;

} else {

error_log(MINOR, "Unrecognized DAS08 analog A1 output range");

error = ERROR;

}

} else if(strncmp("PAI", params, 3) == 0){ portA = DIGITALIN;

} else if(strncmp("PAO", params, 3) == 0){ portA = DIGITALOUT;

} else if(strncmp("PBI", params, 3) == 0){ portB = DIGITALIN;

} else if(strncmp("PBO", params, 3) == 0){ portB = DIGITALOUT;

} else if(strncmp("PCLI", params, 4) == 0){ portCL = DIGITALIN;

} else if(strncmp("PCLO", params, 4) == 0){ portCL = DIGITALOUT;

} else if(strncmp("PCHI", params, 4) == 0){ portCH = DIGITALIN;

} else if(strncmp("PCHO", params, 4) == 0){ portCH = DIGITALOUT;

} else {

error_log(MINOR, "DAS08 argument not recognized");

error = ERROR;

}

}

fgets(params, 200, fp_in);

}

fclose(fp_in);

}

return error;

}

int das08::connect(){

int error;

error = NO_ERROR;

if(ioperm(base, 16, 1) == 0){

DConfigPort(PORTA, portA);

DConfigPort(PORTB, portB);

DConfigPort(PORTCL, portCL);

DConfigPort(PORTCH, portCH);

} else {

error = ERROR;

error_log(MINOR, "Could not connect to DAS08 board - memory is probably in use");

}

return error;

}

int das08::scan(){

int error;

error = NO_ERROR;

// update digital ports

if(portA == DIGITALIN){DIn(PORTA, data_portA);

} else {DOut(PORTA, data_portA[0]);}

if(portB == DIGITALIN){DIn(PORTB, data_portB);

} else {DOut(PORTB, data_portB[0]);}

if(portCL == DIGITALIN){DIn(PORTCL, data_portCL);

} else {DOut(PORTCL, data_portCL[0]);}

if(portCH == DIGITALIN){DIn(PORTCH, data_portCH);

} else {DOut(PORTCH, data_portCH[0]);}

DOut(PORTAUX, data_portXO[0]);

DIn(PORTAUX, data_portXI);

AIn(0, data_AI0);

AIn(1, data_AI1);

AIn(2, data_AI2);

AIn(3, data_AI3);

AIn(4, data_AI4);

AIn(5, data_AI5);

AIn(6, data_AI6);

AIn(7, data_AI7);

// Update analog outputs

AOut(0, data_AO0[0]);

AOut(1, data_AO1[0]);

return error;

}

int das08::disconnect(){

int error;

error = NO_ERROR;

if(ioperm(base, 16, 0) != 0){

error = ERROR;

error_log(MINOR, "Could not release the DAS08 board - memory is probably in use");

}

return error;

}

int das08::DConfigPort(int Port, int Direction){

// This command configures a port as an input or output.

// The Direction field can be either DIGITALIN or DIGITALOUT

// depending on whether the port is to be configured as an

// input or output. Valid ports are PORTA, PORTB, PORTCL and

// PORTCH. Direction bit can be either DIGITALIN or DIGITALOUT.

int error,

//printf("Configuring port %d with direction %d \n", Port, Direction);

error = NO_ERROR;

OldByte = inb(DCONFIGPORT + base); /*read the current register*/

if(Direction == DIGITALIN){ /* determine mask for DIGITALIN */

if(Port == PORTA){ mask = 0x10;

} else if(Port == PORTB){ mask = 0x02;

} else if(Port == PORTC){ mask = 0x09;

} else if(Port == PORTCL){ mask = 0x01; Port = PORTC;

} else if(Port == PORTCH){ mask = 0x08; Port = PORTC;

} else {

error_log(MINOR, "Digital port must be PORTA, PORTB, PORTC, PORTCL or PORTCH");

error = ERROR;

}

NewByte = OldByte | mask; /* new data for register */

} else if(Direction == DIGITALOUT){ /* determine mask for DIGITALOUT */

if(Port == PORTA){ mask = 0xef;

} else if(Port == PORTB){ mask = 0xfd;

} else if(Port == PORTC){ mask = 0xf6;

} else if(Port == PORTCL){ mask = 0xfe; Port = PORTC;

} else if(Port == PORTCH){ mask = 0xf7; Port = PORTC;

} else {

error_log(MINOR, "Digital port must be PORTA, PORTB, PORTC, PORTCL or PORTCH");

error = ERROR;

}

NewByte = OldByte & mask; /* new value for register */

} else {

error_log(MINOR, "Direction must be set to DIGITALIN or DIGITALOUT");

error = ERROR;

}

if(error == NO_ERROR){

//printf("port thingy %d %d \n", NewByte, DCONFIGPORT);

outb(NewByte, DCONFIGPORT + base); /* write config data to register */

}

return error; /* no errors detected */

}

int das08::DBitIn(int Port, int BitNum, int *BitData){

// This function determines whether a bit within the

// requested port is set. The value (1 or 0) is returned

// in the variable pointer sent to the function. Port may

// be PORTA, PORTB, PORTCL or PORTCH. BitNum must be in the

// range 0-7.

int error,

mask = 0, data;

error = NO_ERROR;

if((Port == PORTCL) || (Port == PORTCH)){ data = inb(PORTC + base);

} else { data = inb(Port + base);}

//printf("GOT %d %d %d %d \n", Port, data, BitNum, BitData[0]);

if((Port == PORTA) || (Port == PORTB) || (Port == PORTC)){

if((BitNum >= 0) && (BitNum <= 7)){

} else {

error_log(MINOR, "Bit numbers should be between 0 and 7");

error = ERROR;

}

} else if((Port == PORTCL) || (Port == PORTAUX)) {

if((BitNum >= 0) && (BitNum <= 3)){

} else {

error_log(MINOR, "Bit numbers should be between 0 and 3");

error = ERROR;

}

} else if(Port == PORTCH) {

if((BitNum >= 4) && (BitNum <= 7)){

} else {

error_log(MINOR, "Bit numbers should be between 4 and 7");

error = ERROR;

}

} else if(Port == DCONFIGPORT) {

} else {

error_log(MINOR, "Input port not recognized");

error = ERROR;

}

if(error == NO_ERROR){

BitData[0] = 0;

if((mask & data) != 0) BitData[0] = 1;

}

return error;

}

int das08::DBitOut(int Port, int BitNum, int BitValue){

// This function sets a bit of the requested port to either

// a zero or a one. Port may be PORTA, PORTB, PORTCL or

// PORTCH. BitNum must be in the range 0 - 7. BitValue

// must be 1 or 0.

int error,

error = NO_ERROR;

if((Port == PORTCL) || (Port == PORTCH)){

OldByte = inb(PORTC + base);

} else {

OldByte = inb(Port + base);

}

if((Port == PORTAUX) && (BitValue == 1)){

NewByte = OldByte | mask;

//printf("ddo %x %x \n", mask, OldByte);

} else if((Port == PORTAUX) && (BitValue == 0)) {

NewByte = OldByte & ~mask;

} else if(((Port==PORTA) || (Port==PORTB) || (Port == PORTC)) && (BitValue==1)){

NewByte = OldByte | mask;

}else if(((Port==PORTA) || (Port==PORTB) || (Port==PORTC)) && (BitValue == 0)){

NewByte = OldByte & ~mask;

} else if((Port == PORTCL) && (BitValue == 1)){

NewByte = OldByte | mask;

} else if((Port == PORTCL) && (BitValue == 0)){

NewByte = OldByte & ~mask;

} else if((Port == PORTCH) && (BitValue == 1)){

NewByte = OldByte | mask;

} else if((Port == PORTCH) && (BitValue == 0)){

NewByte = OldByte & ~mask;

} else {

error = ERROR;

}

if((Port == PORTCL) || (Port == PORTCH))

Port = PORTC;

//printf("OUT %d %d\n", NewByte, Port + base);

if(error == NO_ERROR) outb(NewByte, Port + base);

return error;

}

int das08::DIn(int Port, int *Value){

// This function reads the byte value of the specified port

// and returns the result in the variable pointer sent to the

// function. Valid ports are PORTA, PORTB, PORTCL and PORTCH.

int error;

// int result;

// int BitData;

int temp;

error = NO_ERROR;

// if(Port == PORTA){

// result = DBitIn(DCONFIGPORT, 4, &BitData);

// } else if(Port == PORTB){

// result = DBitIn(DCONFIGPORT, 1, &BitData);

// } else if(Port == PORTC){

// result = DBitIn(DCONFIGPORT, 0, &BitData)

// + DBitIn(DCONFIGPORT, 3, &BitData);

// } else if(Port == PORTCL){

// result = DBitIn(DCONFIGPORT, 0, &BitData);

// } else if(Port == PORTCH){

// result = DBitIn(DCONFIGPORT, 3, &BitData);

// } else if(Port == PORTAUX){

// } else {

// error_log(MINOR, "ERROR: Port not recognized");

// error = ERROR;

// }

//////////////

//printf("sss %d %d \n", Port, result);

// if((error == NO_ERROR) && (BitData == 0)){

// error_log("ERROR: Port not configured for read");

// error = ERROR;

// }

if(error == NO_ERROR){

if(Port == PORTCL){

temp = inb(PORTC + base); /* read the port data */

Value[0] = (temp & 0x0f); /* mask off the high bits */

} else if(Port == PORTCH){

temp = inb(PORTC + base); /* read the port data */

Value[0] = (temp & 0xf0); /* mask off the low bits */

} else if(Port == PORTAUX){

Value[0] = 0x7 & (int)((inb(Port + base) / 16));

} else {

Value[0] = 0xff & inb(Port + base); /* read the port data */

}

}

return error;

}

int das08::DOut(int Port, int ByteValue){

// This function writes the byte value to the specified port.

// Valid ports are PORTA, PORTB, PORTCL and PORTCH.

int error;

error = NO_ERROR;

if(Port == PORTAUX){

ByteValue = (0x07 & inb(Port+base)) | (ByteValue * 16);

}

if((ByteValue > 255) || (ByteValue < 0)){

error = ERROR;

}

//printf("Writing byte %d to port %d\n", ByteValue, Port);

if(error == NO_ERROR){

if(Port == PORTCL){

outb((ByteValue & 0x0f), PORTC + base);

} else if(Port == PORTCH){

outb((ByteValue & 0xf0), PORTC + base);

} else {

outb(ByteValue, Port + base); /* write the port data */

}

}

return error; /* no errors detected */

}

int das08::C8254Config(int CounterNum, int Config){

int error,

NewByte,

// TempByte,

// int temp;

error = NO_ERROR;

/* BCD = 0xfe - 16-bit binary count

BCD = 0xf1 - 4 decade Binary Coded Decimal */

BCD = 0xfe;

switch (Config){

case HIGHONLASTCOUNT: mask = 0xf1; break;

case ONESHOT: mask = 0xf3; break;

case RATEGENERATOR: mask = 0xf5; break;

case SQUAREWAVE: mask = 0xf7; break;

case SOFTWARESTROBE: mask = 0xf9; break;

case HARDWARESTROBE: mask = 0xfb; break;

default: error = ERROR;; break;

}

switch (CounterNum){

case 1: counter = 0x3f; break;

case 2: counter = 0x7f; break;

case 3: counter = 0xbf; break;

default: error = ERROR; break;

}

if(error == NO_ERROR){

NewByte = (BCD & mask) & counter;

//printf("The value of TempByte & mask is --> %x.\n", NewByte);

outb(NewByte, CCONFIGPORT + base);

}

return error;

}

int das08::CLoad(int CounterNum, int value)

{

int error;

int TempByte, TempByte1, Register, CounterMask;

int WriteLowByteMask1 = 0x20; /* RL1 | */

int WriteLowByteMask2 = 0xef; /* RL0 & */

int WriteHighByteMask1 = 0xdf; /* RL1 & */

int WriteHighByteMask2 = 0x10; /* RL0 | */

char LowByte[5];

char HighByte[5];

long HighByteValue, LowByteValue;

int test;

error = NO_ERROR;

switch (CounterNum){

default: error = ERROR; break;

}

LowByte[0] = ’0’;

LowByte[1] = ’x’;

if(error == NO_ERROR){

HighByteValue = (int)strtol(HighByte, NULL, 0);

LowByteValue = (int)strtol(LowByte, NULL, 0);

TempByte1 = TempByte & 0xf0;

//printf("The value in config low is --> %x.\n", TempByte1);

outb(TempByte1, CCONFIGPORT + base);

outb(LowByteValue, Register + base);

//printf("The register chosen is --> %x.\n", Register);

test = inb(Register + base);

//printf("The value read in counter low is --> %x.\n", test);

TempByte = (0x30 & WriteHighByteMask1) | WriteHighByteMask2;

//printf("The value in config high is --> %x.\n", TempByte);

outb(TempByte, CCONFIGPORT + base);

outb(HighByteValue, Register + base);

outb(TempByte, CCONFIGPORT + base);

test = inb(Register + base);

//printf("The value in counter high is --> %x.\n", test);

}

return error;

}

int das08::CIn(int CounterNum, int *CountValue){

int error;

int TempByte, Register;

int ReadLowByteMask1 = 0x20; /* RL1 | */

int ReadLowByteMask2 = 0xef; /* RL0 & */

int ReadHighByteMask1 = 0xdf; /* RL1 & */

int ReadHighByteMask2 = 0x10; /* RL0 | */

int CountValue1, CountValue2;

error = NO_ERROR;

switch (CounterNum){

default: error = ERROR; break;

}

if(error == NO_ERROR){

outb(TempByte, CCONFIGPORT + base);

CountValue1 = inb(Register + base);

//printf("The low value is --> %x.\n", CountValue1);

outb(TempByte, CCONFIGPORT + base);

CountValue2 = inb(Register + base);

//printf("The high value is --> %x.\n", CountValue2);

}

return error;

}

int das08::AIn(int ADChannel, int *Value){

// This function requires three arguments to perform the

// analog to digital conversion. ADChannel must be in the

// range 0-7 and Range must be a valid range code

// i.e. BIP5VOLTS. The value of the conversion will be

// returned to the address specificed through the pointer

// variable. This value will be in the range 0-4095.

int error;

int value1, value2, value3, curr_status, new_status, ADbusy;

int EOC = 1;

error = NO_ERROR;

curr_status = inb(ADCSTATUS + base); /* current value in status */

default:error = ERROR;; break; /* error */

}

if(error == NO_ERROR){

outb(chan0, GAIN + base); /* set the gain/range value */

outb(new_status, ADCSTATUS + base); /* set the channel number */

outb(0x00, ADCLOW + base); /* start a 12 bit A/D conversion */

}

while((error == NO_ERROR) && (EOC == 1)){ /* check for end of conversion */

ADbusy = inb(ADCSTATUS + base); /* read status register */

EOC = 1; /* A/D still converting */

} else {

EOC = 0; /* A/D done converting */

}

}

if(error == NO_ERROR){

ADValue_low = inb(ADCLOW + base); /* get the lower eight bits */

ADValue_high = inb(ADCHIGH + base); /* get the upper four bits */

case 0x00:value1 = 0;break;

case 0x80:value1 = 1;break;

case 0x40:value1 = 2;break;

case 0xc0:value1 = 3;break;

case 0x20:value1 = 4;break;

case 0xa0:value1 = 5;break;

case 0x60:value1 = 6;break;

case 0xe0:value1 = 7;break;

case 0x10:value1 = 8;break;

case 0x90:value1 = 9;break;

case 0x50:value1 = 10;break;

case 0xd0:value1 = 11;break;

case 0x30:value1 = 12;break;

case 0xb0:value1 = 13;break;

case 0x70:value1 = 14;break;

case 0xf0:value1 = 15;break;

default:error = ERROR;break;

}

ADValue_low1 = (ADValue_low & 0x0f); /* mask off bits 4-7 */

case 0x00:value2 = 0;break;

case 0x01:value2 = 16;break;

case 0x02:value2 = 32;break;

case 0x03:value2 = 48;break;

case 0x04:value2 = 64;break;

case 0x05:value2 = 80;break;

case 0x06:value2 = 96;break;

case 0x07:value2 = 112;break;

case 0x08:value2 = 128;break;

case 0x09:value2 = 144;break;

case 0x0a:value2 = 160;break;

case 0x0b:value2 = 176;break;

case 0x0c:value2 = 192;break;

case 0x0d:value2 = 208;break;

case 0x0e:value2 = 224;break;

case 0x0f:value2 = 240;break;

default:error = ERROR;break;

}

ADValue_low2 = (ADValue_low & 0xf0); /* mask off bits 0-3 */

case 0x00:value3 = 0;break;

case 0x10:value3 = 256;break;

case 0x20:value3 = 512;break;

case 0x30:value3 = 768;break;

case 0x40:value3 = 1024;break;

case 0x50:value3 = 1280;break;

case 0x60:value3 = 1536;break;

case 0x70:value3 = 1792;break;

case 0x80:value3 = 2048;break;

case 0x90:value3 = 2304;break;

case 0xa0:value3 = 2560;break;

case 0xb0:value3 = 2816;break;

case 0xc0:value3 = 3072;break;

case 0xd0:value3 = 3328;break;

case 0xe0:value3 = 3584;break;

case 0xf0:value3 = 3840;break;

default: error = ERROR; /* error - unknown conversion result */

}

*Value = value1+value2+value3; /* total value for conversion */

}

return error; /* no errors detected */

}

int das08::AOut(int DAChannel, int DAValue){

// This function performs a digital to analog conversion

// routine. The DAChannel must be either 0 or 1 and the

// digital value must be in the range 0-4095.

int error;

int low, high, DACLOW, DACHIGH;

error = NO_ERROR;

switch(DAChannel){

case 0:DACLOW = DAC0LOW;DACHIGH = DAC0HIGH;break;

case 1:DACLOW = DAC1LOW;DACHIGH = DAC1HIGH;break;

default:error = ERROR;break;

}

/* The following table converts the digital value into

three hex values encompassing two 8-bit registers. The

layout of the registers follow:

low - DA7 DA6 DA5 DA4 DA3 DA2 DA1 DA0

high - x x x x DA11 DA10 DA9 DA8 */

if(DAValue <= 255){

low = DAValue;

high = 0x00;

} else if((DAValue >= 256) && (DAValue <= 511)){

low = DAValue - 256;

high = 0x01;

} else if((DAValue >= 512) && (DAValue <= 767)) {

low = DAValue - 512;

high = 0x02;

} else if((DAValue >= 768) && (DAValue <= 1023)) {

low = DAValue - 768;

high = 0x03;

} else if((DAValue >= 1024) && (DAValue <= 1279)) {

low = DAValue - 1024;

high = 0x04;

} else if((DAValue >= 1280) && (DAValue <= 1535)) {

low = DAValue - 1280;

high = 0x05;

} else if((DAValue >= 1536) && (DAValue <= 1791)) {

low = DAValue - 1536;

high = 0x06;

} else if((DAValue >= 1792) && (DAValue <= 2047)) {

low = DAValue - 1792;

high = 0x07;

} else if((DAValue >= 2048) && (DAValue <= 2303)){

low = DAValue - 2048;

high = 0x08;

} else if((DAValue >= 2304) && (DAValue <= 2559)){

low = DAValue - 2304;

high = 0x09;

} else if((DAValue >= 2560) && (DAValue <= 2815)){

low = DAValue - 2560;

high = 0x0a;

} else if((DAValue >= 2816) && (DAValue <= 3071)){

low = DAValue - 2816;

high = 0x0b;

} else if((DAValue >= 3072) && (DAValue <= 3327)){

low = DAValue - 3072;

high = 0x0c;

} else if((DAValue >= 3328) && (DAValue <= 3583)){

low = DAValue - 3328;

high = 0x0d;

} else if((DAValue >= 3584) && (DAValue <= 3839)){

low = DAValue - 3584;

high = 0x0e;

} else if((DAValue >= 3840) && (DAValue <= 4095)){

low = DAValue - 3840;

high = 0x0f;

} else{

error = ERROR; /* error - D/A value must be 0-4095 */

}

if(error == NO_ERROR){

outb(low, DACLOW + base); /* write the low byte value */

outb(high, DACHIGH + base); /* write the high byte value */

}

return error; /* no errors detected */

}

Listing 16.1 - DAS08 Driver Test File (testdaq.cpp)

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include "das08_io.h"

int ChooseCounter();

int ChooseConfig();

int ChooseDir(int DirectNum);

#define QUERY 350

#define CHOOSE_PORT 351

#define CHOOSE_COUNTER 352

#define CHOOSE_CONFIG 353

#define CHOOSE_DIRECTION 354

int query(int, char*, int);

int main(){

int choice;

das08 *A;

int value;

A = new das08();

A->configure("das08.conf");

A->connect();

do{

printf("\n\n------------ DAS08 Test Harness Menu --------------\n");

printf("1. Digital Configure\n");

printf("2. Digital Input Bit\n");

printf("3. Digital Input Word\n");

printf("4. Digital Output Bit\n");

printf("5. Digital Output Word\n\n");

printf("6. Counter Configure\n");

printf("7. Counter Load Value\n");

printf("8. Counter Input Value\n\n");

printf("10. Analog Output Value\n\n");

printf("11. Quit\n\n");

printf("Select: ");

scanf("%d", &choice);

if(choice == 1){

A->DConfigPort( query(CHOOSE_PORT, NULL, 0),

query(CHOOSE_DIRECTION, NULL, 0));

} else if(choice == 2){

A->DBitIn( query(CHOOSE_PORT, NULL, 0),

query(QUERY, "Choose a bit (0-7): ", 0), &value);

printf("The Bit Value is [%d] \n", value);

} else if(choice == 3){

A->DIn( query(CHOOSE_PORT, NULL, 0), &value);

printf("The Value is [%d] or [%d]hex\n", value, value);

} else if(choice == 4){

A->DBitOut( query(CHOOSE_PORT, NULL, 0),

query(QUERY, "Choose a bit (0-7): ", 0),

query(QUERY, "Choose a value (0 or 1): ", 0));

} else if(choice == 5){

A->DOut( query(CHOOSE_PORT, NULL, 0),

query(QUERY, "Choose a value (-128 to 127): ", 0));

} else if(choice == 6){

A->C8254Config( query(CHOOSE_COUNTER, NULL, 0),

query(CHOOSE_CONFIG, NULL, 0));

} else if(choice == 7){

A->CLoad( query(CHOOSE_COUNTER, NULL, 0),

query(QUERY, "Enter a value in the form 0x____ : ", 0));

} else if(choice == 8){

A->CIn( query(CHOOSE_COUNTER, NULL, 0), &value);

printf("The Counter value was [%d]\n", value);

} else if(choice == 9){

A->AIn( query(QUERY, "Enter Channel Number (0-7): ", 0), &value);

printf("The value is [%d]\n", value);

} else if(choice == 10){

A->AOut( query(QUERY, "Enter Channel Number (0-1): ", 0),

query(QUERY, "Enter Value (0- 4095): ", 0));

} else if(choice == 11){

} else {

printf("ERROR: Choice not recognized\n");

}

} while(choice != 11);

A->disconnect();

delete A;

}

int query(int type, char *text, int def){

char work[20];

int value;

if(type == QUERY){

printf("%s [%d]: ", text, def);

scanf("%s", work);

printf("<%s>\n", work);

if(strlen(work) == 0){

return def;

} else {

return atoi(work);

}

} else if(type == CHOOSE_PORT){

printf("Which port (1=A, 2=B, 3=C, 4=CH, 5=CL, 6=AUX): ");

scanf("%d", &value);

if(value == 1) return PORTA;

if(value == 2) return PORTB;

if(value == 3) return PORTC;

if(value == 4) return PORTCL;

if(value == 5) return PORTCH;

if(value == 6) return PORTAUX;

return ERROR;

} else if(type == CHOOSE_COUNTER){

printf("Which counter (1, 2, 3): ");

scanf("%d", &value);

if((value >= 1) || (value <= 3)) return value;

return ERROR;

} else if(type == CHOOSE_CONFIG){

printf("Which mode (1=HighOnLastCount, 2=OneShot, 3=RateGenerator, 4=SquareWave, 5=SoftwareStrobe, 6=HardwareStrobe): ");

scanf("%d", &value);

if(value == 1) return HIGHONLASTCOUNT;

if(value == 2) return ONESHOT;

if(value == 3) return RATEGENERATOR;

if(value == 4) return SQUAREWAVE;

if(value == 5) return SOFTWARESTROBE;

if(value == 6) return HARDWARESTROBE;

return ERROR;

} else if(type == CHOOSE_DIRECTION){

printf("Which direction (1=In, 2=Out): ");

scanf("%d", &value);

if(value == 1) return DIGITALIN;

if(value == 2) return DIGITALOUT;

return ERROR;

} else {

return ERROR;

}

}

void error_log(int code, char *string){

printf("ERROR %d: %s \n", code, string);

}

### 21.8 Summary

• A/D conversion will convert a continuous value to an integer value.

• D/A conversion is easier and faster and will convert a digital value to an analog value.

• Resolution limits the accuracy of A/D and D/A converters.

• Sampling too slowly will alias the real signal.

• Analog inputs are sensitive to noise.

• The analog I/O cards are configured with a few words of memory.

• BTW and BTR functions are needed to communicate with the analog I/O cards.

### 21.9 Practice Problems

1. You need to read an analog voltage that has a range of -10V to 10V to a precision of +/-0.05V. What resolution of A/D converter is needed?

2. We are given a 12 bit analog input with a range of -10V to 10V. If we put in 2.735V, what will the integer value be after the A/D conversion? What is the error? What voltage can we calculate?

3. We need to select a digital to analog converter for an application. The output will vary from -5V to 10V DC, and we need to be able to specify the voltage to within 50mV. What resolution will be required? How many bits will this D/A converter need? What will the accuracy be?

4. Write a program that will input an analog voltage, do the calculation below, and output an analog voltage.

5. Develop a program to sample analog data values and calculate the average, standard deviation, and the control limits. The general steps are listed below.

1. Read sampled inputs.

2. Randomly select values and calculate the average and store in memory. Calculate the standard deviation of the stored values.

3. Compare the inputs to the standard deviation. If it is larger than 3 deviations from the mean, halt the process.

4. If it is larger than 2 then increase a counter A, or if it is larger than 1 increase a second counter B. If it is less than 1 reset the counters.

5. If counter A is =3 or B is =5 then shut down.

6. Goto 1.

### 21.10 Laboratory - Interfacing to a DAQ Card

Purpose:

To use a data aquisition card to aquire data.

Overview:

The daq card will be placed into a Linux computer and then controlled with the drive programs listed in this chapter.

Pre-Lab:

1. Visit the computer boards web site (www.computerboards.com) and review the manual for the DAS-08 ISA board.

In-Lab:

1. Complete the tutorial for the DAS-08 DAQ card.

2. Modify the tutorial program so that the analog input value from the board is read once a second and written to a database.

Submit (individually):

1. The program developed during the laboratory.