The ML16-P Linux Users Guide





1. Introduction


1.1. Overview

This document describes how to interact with the ML16-P analog/digital I/O board from the Linux operating system. The ML16-P is a low-quality, low-cost real-world interface for the ISA bus. It is produced and sold by Industrial Computer Source. This document is not intended to replace the Model ML16-P Product Manual provided by Industrial Computer Source, but rather to supplement it. In other words, the ML16-P Linux Users Guide is a guide to the Linux driver for the ML16-P board, not to the board itself.

This document will start out with some general information about the mechanics of accessing the I/O cards, followed by detailed information about each of the five features of the ML16-P board in turn:


1.2. Filesystem Nodes

The ML16-P driver (like most drivers) interfaces to the user via the file system. In the /dev directory you will find several special files with names of the form ml16pX-Y. X is a letter which identifies which physical I/O board the node refers to. The board identifiers are a for the first board, b for the second, etc. Y is a mnemonic identifier for the function on the board that the node refers to:

Device File Name Read/Write Usage
adc{0-15} Read-Only Read the values of the ADCs (analog input lines)
dac{0-1} Write-Only Set the values of the DACs (analog output lines)
digital Read/Write Read the values of the digital input lines, set the values of the digital output lines
ct{0-2} Read/Write Get and set the counts of the counter/timers

The digital I/O node does double duty by providing an interface to two different functions: the digital input lines and the digital output lines. Though the two functions share a file system node, they are considered separate resources. If one process has open()ed /dev/ml16p?-digital with the access flag O_RDONLY, then that process is only controlling one of the two resources, namely the digital input lines. The other resource, the digital output lines, is still avaliable, and can be requested with an open() call to the same file system node but with the access flag O_WRONLY.


1.2.1. Command Line Access

Since the driver is accessible in the file system, you can use all your favourite command-line file manipulation tools to interact with it.

Reading the values of the digital input lines from the command line can be easily done with the dd(1) command. For example:
shell-prompt> dd if=/dev/ml16pa-digital of=digital-data bs=1 count=1
will read one byte from the digital input lines and write it to the file digital-data.

Setting the value of the digital lines can be easily done with a variety of commands like echo, cp, dd, and cat. For example, the command:
shell-prompt> stty -icanon; cat > /dev/ml16pa-digital
will send each key typed on the keyboard to the digital output lines.

A command like:
shell-prompt> ls > /dev/ml16pa-digital
will send each byte of the output of the ls command to the digital output lines.

These techniques can also be used to read analog voltages from the ADCs, or to write analog voltages to the DACs. Accessing the analog devices is a little more difficult because of the mapping of bytes to actual voltages. A special tool in the included program "support/volts.c" makes this much easier.


1.2.2. Access from Within a Program

Accessing the ML16-P board from a program is done by calling the open(2), read(2), write(2), ioctl(2), and close(2) system calls on the filesystem nodes described in section 1.2., Filesystem Nodes above.

The open() call does all the neccessary preparations for accessing the board, including function locking. This needs to be done before any read()s, write()s, or ioctl()s can be used.

The read() call, as expected, reads data from the specified function of the I/O board. This is used to read the values on the digital input lines, to read the voltages on the ADCs, and to read the count of the counter/timers.

The write() call, conversely, writes data to a specific function of the I/O boards. A write() can set the values of the digital output lines, set the voltage of a DAC, or set the count of a counter/timer.

The ioctl() call is used to control the various software selectable features of the i/o board. With ioctl()s, a program can set individual bits of the digital output lines, control the input range and input polarity of an ADC, change the mode of a counter/timer, as well as recieve information about the current configuration of the hardware.

The close() call should be used when all device access is done, usually just before the program terminates. It undoes all that the open() call did and prepares the device for another open(). After a close() call, the resource is no longer allocated to the calling process, and read()s, write()s, etc, will fail.

#include <ml16p.h> for the relevant ioctls and other constants.


1.3. A Word of Warning about the Termination Panels

If you are using the model ML-16TK Termination Panels there are some peculiarities you should be aware of.


1.4. The Initialized State

When a card is initialized, the ADCs are set up for offset binary mode, unipolar input, and high input range (0 VDC to +10 VDC). The DACs are not initialized. The digital output lines are set to 0x00, all logical LOW. The counter/timers are configured for Mode 2, though no count is loaded.



2. Analog Inputs

There are eight or 16 8-bit Analog to Digital Converters (ADCs) on each ML16-P board, depending on how it is configured. Only one ADC can be used at a time. Actually, both of the two preceeding sentences are lies; see "doc/HARDWARE" for the truth. But from a user's point of view, it seems like they are true, and this documentation will treat the analog input capabilities of the ML16-P board as if that was the case.

The values returned by reading the ADCs are in binary form, and are not obviously human-readable. While this is ok for signal processing, etc, it is not nice to present to a user. The values can be converted to human-readable form, for example using the conversion functions that are demonstrated in the program "support/volts.c". The conversion function for outputting a specific voltage from the DACs is also given there.


2.1. Input Range

Each ADC can be individually configured for one of four possible input ranges. There are two independent factors, each with two possible values, that combine to determine the input range of an ADC. The factors can be called Input Polarity and Range Size. An ADC can have Bipolar polarity or Unipolar polarity; the range can be High or Low. The effective acceptable input range is determined from these two factors as follows:

Input Polarity
Unipolar Bipolar Resolution
Range Size High 0.000 VDC to +9.961 VDC -5.000 VDC to +4.961 VDC 0.039 VDC
Low 0.000 VDC to +0.255 VDC -0.128 VDC to +0.127 VDC 0.001 VDC

When the driver module is loaded, all analog input lines are configured for High range and Unipolar polarity. These settings can be changed by the user at any time (any time between an open() and a close() call, that is), by using ioctl()s. It is strongly reccommended that you explicitly configure the ADCs you intend to use yourself before accessing them. The relevant ioctls are listed and documented in section 2.1.1., Range ioctl()s and in "ml16p.h", under your normal include directory (usually "/usr/include"). The file "support/range.c" demonstrates the use of ioctl()s to get and set the current configuration of an ADC. The settings of an ADC change only when a user program changes them.

The header file "ml16p.h" also includes the constants from the table above for easy voltage computation.


2.1.1. Range ioctl()s

Four ioctl()s are defined for changing the input range of an ADC:

A fifth ioctl returns the current setting of an ADC. This ioctl is called ML16P_IO_INFO, and it writes a byte to the unsigned char pointed to by its argument. The byte is a bitmapped description of the current state of the ADC in question. The ioctl and the bit map is described in the header file "ml16p.h".


2.2. Accessing the ADC

The ADCs are accessed by read()ing from "/dev/ml16p?-adc*". Reading a voltage from a specific ADC is done by calling the read() system call on a previously open()ed ADC special file. This could be done as:
read(devicefile, val, count);

The above read() will deposit count bytes from the previously open()ed device devicefile into the array val. Each byte is one 8-bit reading from the ADC corresponding to devicefile. The readings are taken consecutively at high speed (sometimes exceeding the max rate reported by the manufacturer). Tests on the two cards i have access to indicate an actual throughput of just under 38KHz. Your mileage may vary here.

To test the throughput of the ADC you're using, run a command like:
time dd if=/dev/ml16pa-adc0 of=/dev/null bs=10000 count=10
This will take 100,000 samples, and tell you how long it took. Divide 100,000 by the number of seconds elapsed, and you will have your throughput Note that since all the analog input lines actually use the same physical ADC chip, they all operate at the same speed. You need only test one of them and you will know the speed of all.

The readings acquired are binary coded and suitable for processing, but not for human consumption. A read value of 0x00 indicates that that the ADC read the lowest possible voltage it could. Each step above 0x00 is an increase of one times the resolution. Thus, if an ADC configured for low input range and bipolar input, and a read() reads 0x4b, then the voltage at the ADC was (0 + (0x4b * 0.001)). The general formula for converting a binary reading (call it val) off the ADC to a floating-point voltage is:
((val / 255) * (voltage_max - voltage_min)) + voltage_min

Here, voltage_min and voltage_max are dependent on the setting of the ADC that the reading (val) was taken at. See "support/volts.c" for an example. The various constants (voltage minimums, maximums, and resolutions) are defined in the header file "ml16p.h".


2.3. Electrical Characteristics

The ADCs are protected against +/- 40 VDC continuous overvoltage per channel. This means that you can feed it 40 Volts without it melting. regardless, this is probably not a good idea.

The ADC has an input impedance of 10 Megohms. Thus, at +40 Volts, the ADC will draw 40/(10*10^6) = 4 micro Amps of current and consume 160 micro Watts of power.



3. Analog Outputs

The ML16-P board has two 8-bit analog output channels (DACs). The DACs are used to provide a specific voltage to an external device. Note that the DACs cannot provide much current. We suggest feeding the output of the DACs to high-impedance amplifiers or buffers in order to avoid sucking too much current out of them. Though electrically more finicky than the ADCs, the DACs are much simpler operationally. The only thing you can do to a DAC is tell it what voltage to output.


3.1. Voltage Output

To get a DAC to output a specific voltage, you send it one byte (8 bits). The DACs have an output range of -10.000 VDC to +9.922 VDC. The DACs accept input in much the same way the ADCs provide output. A 0x00 sent to a DAC will cause it to output the lowest voltage possible (-10.000 VDC). A 0xff will cause it to output the largest possible voltage (+9.922 VDC). As with the ADCs, the range constants for the DACs are listed in "ml16p.h".

The range of the DACs can not be changed. The resolution is fixed at 78 mV.

The DACs are accessed by writing to /dev/ml16p?-dac{0,1}.


3.2. Electrical Characteristics

Each DAC can provide 5 mA maximum. Dont draw more current or you will melt your computer.

Each DAC can output a voltage in the -10.000 VDC to +9.922 VDC range, with a resolution of 78 mV.



4. Digital Inputs

There are eight digital input lines on the ML16-P board. The input lines are accessed by reading "/dev/ml16p?-digital".


4.1. Electrical Characteristics

TTL lows are 0.0 VDC to 0.4 VDC. TTL highs are 2.4 VDC to 24 VDC. It is probably a good idea to use 2.4 VDC to 5.0 VDC as logic high, since this is the standard convention.

As noted in section 1.3., A Word of Warning about the Termination Panels, if you are attaching external input lines to the digital input points on the Termination Panels, be sure to put the switches in the OFF position.



5. Digital Outputs

There are eight digital output lines on the ML16-P board. These output lines can be accessed by writing to /dev/ml16p?-digital, or by calling the SETBIT ioctl on those device files.


5.1. The SETBIT ioctl()

The ML16P_IO_SETBIT ioctl manipulates individual bits of the digital output lines. It takes an argument of type long. Bits 6-4 of this long specify which output line (bit) to change. Bit 0 of the long argument specifies the new value for this bit. For example, calling the ML16P_IO_SETBIT ioctl with the argument 0x41L would cause bit 4 of the digital output lines to be set high (logical 1). Calling the ioctl with argument 0x70L causes the most significant bit to be set low (logical 0).

To use ioctls, you need to #include <sys/ioctl.h> (in addition to #include <ml16p.h>, of course). See the file "support/setbit.h" for an example of how to use this feature.


5.2. Electrical Characteristics

Each digital output line can source 24 mA. Logical lows are guaranteed to be in the 0.0 VDC to 0.4 VDC range, and highs are in the 2.5 VDC to 5.0 VDC range.



6. Counter/Timers

The ML16-P card has an Intel 8253 Counter/Timer chip, which provides 3 independent counter/timers.

To load a count, write 2 bytes (least significant byte first) to the appropriate device. An easy way to do this is to put the desired value into an unsigned short, then write() the short to the device file. If a write() is requested with some number of bytes other than 2, then the call will fail with error ENOSPC, and no value will be written to the counter.

To get the current value of a counter, simply read() 2 bytes from the appropriate device file. If the read() requested is not of 2 bytes, then the call will fail with error ENODATA, and no other action is performed. If the read() requested is of exactly 2 bytes, then the read will return the current value of the counter, LSB first. A convenient way to get the value of a counter is to read() 2 bytes into an unsigned short.

See the Model ML16-P Product Manual for a description of the various counter/timer modes.