Analog input

The pcl711_read_analog() function is used to handle the analog input:

#define PCL711_DELAY    1000  // 1 us

int pcl711_read_analog (pcl711_t *pcl, int channel)
  int   data, base, timeout;
  static int calibrated = 0;
  static struct timespec when;

  // 1) calibrate nanospin if required
  if (!calibrated) {
    nanospin_calibrate (1);      // with interrupts off
    nsec2timespec (&when, PCL711_DELAY);
    calibrated = 1;

  // 2) ensure we are in range
  channel &= 7;
  base = pcl -> port;

  // 3) select the channel
  out8 (base + PCL711_MUX_SCAN_CONTROL, channel);

  // 4) select the gain
  out8 (base + PCL711_GAIN_CONTROL, pcl -> gains [channel]);

  // 5) trigger the conversion
  out8 (base + PCL711_SOFTWARE_AD_TRIGGER, 0 /* any data */);

  // 6) wait for the conversion
  timeout = PCL711_TIMEOUT;
  do {
    data = in8 (base + PCL711_ANALOG_HIGH);
    nanospin (&when);     // spin
  } while ((data & PCL711_ANALOG_HIGH_DRDY) && (timeout-- >= 0));

  // 7) indicate timeout if any
  if (timeout < 0) {
    return (-1);

  // 8) return data
  data = ((data & 0x0f) << 8) + in8 (base + PCL711_ANALOG_LOW);
  return (data);

The code performs the following steps:

  1. If we haven't already done so (in a previous invocation) calibrate the nanospin() values, and set up a time delay (PCL711_DELAY is 1000, or 1 microsecond).
  2. The channel number is clamped to the range of 0 through 7. This is a sanity enforcement within the code. We get the I/O port base address from the pcl711_t structure (see the section after step 8, below).
  3. We write the desired channel number into the multiplexer control register. This doesn't start the conversion yet, but selects the input channel only.
  4. We write the desired gain to the gain control register. Notice how the gains are stored in the extended attributes structure, just as the base register was stored there and used in step 3 above.
  5. When we write any data to the software A/D trigger register, the PCL-711 card begins the data conversion. We've already gated the source into the gain amplifier, and the converter takes the output of the gain amplifier and converts it. The manual says that this operation takes on the order of microseconds.
  6. Here we poll for the conversion to be complete (see the section after step 8, below). When the data is ready, the PCL711_ANALOG_HIGH_DRDY bit will go low, and we'll exit the loop (we also exit on timeout).
  7. If there was a timeout, we return the special value of -1 to whoever called us.
  8. Finally, our data is ready. We use the lowest four bits of the data register we used for polling (these four bits end up being bits 8 through 11 of the result) and we add that to the least-significant part of the value (from the PCL711_ANALOG_LOW register).

Notice that the first parameter, pcl, is of type pointer to pcl711_t. The pcl711_t is the extended attributes structure used in the resource manager. It's a convenient place to store additional information, such as the base port address.

Notice that in step 6 we are polling. While polling is generally frowned upon in realtime control systems, we have no choice. The manual states that the conversion will take place within microseconds, so the overhead of giving up the CPU and letting another thread run is going to be the same as, or greater than, the time it takes to poll and get the data. So we might as well poll. Note also that we don't poll forever; we poll only for PCL711_TIMEOUT number of iterations.

The polling is in place to handle hardware that's not present—if the hardware is missing (or defective), we will time out. Also, notice that we use nanospin() to give up the ISA bus between polling. The nanospin() delay value is selected to be 1 microsecond; this ensures that we poll "often enough" to minimize the number of times we poll.

In conjunction with the pcl711_read_analog() function, there's a function that sets the gain value. While we could have written the gain value directly into the extended attributes structure's gain member, it's much nicer to have a function to do it, so that we can isolate accesses to that parameter (and change things around if we need to, without changing a bunch of code).

This function is pcl711_set_gain():

pcl711_set_gain (pcl711_t *pcl, int channel, int gaincode)
  if (gaincode < 0 || gaincode > 4) {
  channel &= 7;
  pcl -> gains [channel] = gaincode;

Notice the sanity checking up front to ensure that no bad values are used before we write the value into the gains array member.