Digitale Ein-/Ausgänge

Mit Hilfe der LIBAD4 Bibliothek ist es sehr einfach, einen Digitalkanal auszulesen oder zu setzen. Dazu öffnet das Beispiel Messsystem (ad_open), stellt dann die Ein-/Ausgaberichtung des Kanals ein (ad_set_line_direction) und liest dann entweder den Kanals aus (ad_discrete_in) oder setzt den Kanal (ad_discrete_out). Zuletzt wird das Messsystem wieder geschlossen (ad_close). Natürlich würde in einem realen Programm das Messsystem nur einmal am Beginn des Programms geöffnent und am Ende wieder geschlossen werden. Auch die Richung des Digitalkanals muss nur einmal eingstellt werden, d.h. es ist es nicht nötig ad_set_line_direction vor jedem Auslesen oder Setzen aufzurufen.

Der Sourcecode des Beispiels befindet sich im LIBAD4 SDK im Verzeichnis examples/digital_io (und dort jeweils in den Verzeichnissen ccs und vb für C/C++, C# und Visual Basic™ .Net). Wie alle Beispiele, verzichtet auch dieses aus Gründen der Einfachheit auf eine solide Fehlerbehandlung...

Die Messsysteme stellen die digitalen Leitungen organisiert in Digitalkanälen zur Verfügung. Der Aufbau ist vom Messsystem abhängig, so verfügt zum Beispiel die USB-PIO über 3 Digitalkanäle mit je acht Leitungen. Es werden immer alle Leitungen eines Digitalkanals auf einmal gelesen, gesetzt oder in der Richtung verändert (also im Beispiel der USB-PIO immer acht Leitungen auf einmal). Die Anzahl der Ports und Leitungen in einem Port ist im LIBAD Handbuch in Kapitel 6, Messsystem, beschrieben.

Das Beispiel verwendet die Routine do_digital_io() um den Aufruf der LIBAD4 Funktionen ad_open()ad_set_line_direction()ad_discrete_in()ad_discrete_out() und ad_close() zu zeigen. An diese Routine wird der Name des Messgeräts (driver), die Ein-/Ausgaberichtung (dir), die Nummern der Digitalkanäle (chav) und die auszugebenden Werte (datav) übergeben. Die C/C++ Variante übergibt zusätzlich noch die Anzahl der Kanäle (chac). Alle Argumente werden in main() von der Kommandozeile gewonnen, entsprechend aufbereitet und dann an do_digital_io() übergeben.

Der erste Parameter auf der Kommandozeile wird in driver übergeben und legt das Messsystem fest. Beispielsweise öffnet "usb-pio:@100" die USB-PIO mit der Seriennumer 100. Ausführliche Hinweise zu den notwendigen Namen für die verschiedenen Messsysteme finden Sie im LIBAD4 Handbuch in Kapitel 6, Messsysteme.

Dieser Name wird von do_digital_io() direkt an ad_open() weitergegeben. Der Rückgabewert von ad_open() ist ein Handle, der das geöffnete Messsystem repräsentiert und an alle Funktionen der LIBAD4 übergeben werden muss. Im Fehlerfall wird -1zurückgegeben.

Der Parameter dir definiert die Ein-/Ausgaberichtung und wird in main() durch das optionale Kommandzeilenargument -o oder -i bestimmt. Per default steht dieser Wert auf INPUT und sorgt so für das Auslesen des Digitalkanals.

Die restlichen Argumente auf der Kommandzeile werden von main() in Zahlen konvertiert und in die Felder chav[] und datav[] geschrieben (wobei datav[] natürlich nur im Ausgabefall gefüllt wird). In einer Schleife über alle Kanäle stellt do_digital_io() dann die Richtung des Digitalkanals ein. Dazu wird an ad_set_line_direction das Messsystem (adh, wie von ad_open() geliefert), der Kanal (bestehend aus dem Kanaltyp AD_DIGITAL_IO und der Nummer des Kanals chav[i]) und die Richtung (dir) übergeben. Die übergebene Richtung ist ein Bitfeld, mit dem alle Leitungen eines Ports eingestellt werden. Dabei eintspricht ein gesetztes Bit einem Eingang (1 ==> Input) und ein zurückgesetzes Bit einem Ausgang (0 ==> Ausgang).

Abhängig von der eingestellen Richtung wird entweder ad_discrete_in oder ad_discrete_out aufgerufen. Für beide Funktionen spezifiert das erste Argument (adh, wie von ad_open() geliefert) das Messsystem, das zweite Argument den Kanal (bestehend aus dem Kanaltyp AD_DIGITAL_IO und der Nummer des Kanals chav[i]) und das dritte Argument den Messbereich (für Digitalkanäle immer 0). Als letztes Argument wird datav[i] übergeben, das bei ad_discrete_in den aktuellen Wert am Digitalkanal aufnimmt, bzw. bei ad_discrete_out den Ausgabewert zur Verfügung stellt. Beide Funktionen geben im Fehlerfall einen Wert ungleich null zurück, wobei diese Fehlernummer immer einer Fehlernummer des Hostrechners entspricht (also z.B. unter Windows wird eine Fehlernummer aus <winerror.h> zurückgegeben).

Danach wird der gemessene (oder neu gesetzte) Wert auf der Konsole ausgegeben. Neben einer Ausgabe in Hex gibt das Beispiel auch noch den Zustand ein einzelnen Leitungen des Kanals aus (wobei immer 16 Leitungen ausgegeben werden, auch wenn beispielsweise die USB-PIO nur acht Leitungen in einem Digitalkanal hat und deswegen auch nur die unteren acht Bits sinnvoll sind).

Nach dem Schleifenende wird das Messsystem mit ad_close() wieder geschlossen.

/* Libad Digital I/O Example
 *
 * Example showing how to set and get the state of some digital inputs
 * using bmcm's LIBAD4.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include "libad.h"
/* Line Direction Masks
 */
#define OUTPUT 0x0000
#define INPUT  0xffff
/* Get/Set Digital input(s)/output(s).
 */
void
do_digital_io (const char *driver, int dir, int chac, int chav[], uint32_t datav[])
{
  int32_t adh;
  int i;
  
  /* Open DAQ system.
   */
  adh = ad_open (driver);
  if (adh == -1)
    {
      printf ("failed to open %s: err = %d\n", driver, errno);
      return;
    }
  /* Set/get digital i/o.
   */
  for (i = 0; i < chac; i++)
    {
      uint32_t mask;
      int rc;
      /* Setup port's direction.
       */
      ad_set_line_direction (adh, AD_CHA_TYPE_DIGITAL_IO | chav[i], dir);
      /* Either read from port or write to port.
       */
      if (dir == INPUT)
        rc = ad_discrete_in (adh, AD_CHA_TYPE_DIGITAL_IO | chav[i], 0, &datav[i]);
      else
        rc = ad_discrete_out (adh, AD_CHA_TYPE_DIGITAL_IO | chav[i], 0, datav[i]);
      if (rc == 0)
        {
          printf ("cha %d: port = 0x%04x, lines 16..1 = ", chav[i], datav[i]);
          /* Separate lines of that port.
           */
          for (mask = 0x8000; mask != 0; mask >>= 1)
            {
              if (datav[i] & mask)
                printf ("1");
              else
                printf ("0");
            }
          printf ("\n");
        }
      else
        printf ("error: failed to read/write cha %d: err = %d\n", chav[i], rc);
    }
  /* Close DAQ system again.
   */
  ad_close (adh);
}
/* Show usage.
 */
void
usage ()
{
  printf ("usage: digital_io <driver> [-i] <cha1> .. <chan>\n"
          "       digital_io <driver> -o [<cha1>,]<val1> .. [<chan>,]<valn>\n"
          "  <driver>           string to pass to ad_open()\n"
          "                     - will prompt for name\n"
          "  <cha1> .. <chan>   number of digital port\n"
          "  <val1> .. <valn>   value to set digital output to\n");
}
/* Main entry point.
 */
int
main (int argc, char *argv[])
{
  if (argc > 1)
    {
      char *name, *p, tmp[80];
      int i, start, dir, chac, chav[16];
      uint32_t datav[16];
      /* First command line argument is the DAQ's name.
       * If "-" is passed, then let's read the name from
       * the console.
       */
      name = argv[1];
      if (strcmp (argv[1], "-") == 0)
        {
          printf ("data acquisition system to open: ");
          fgets (tmp, sizeof(tmp), stdin);
          p = strchr (tmp, '\n');
          if (p)
            *p = 0;
          name = tmp;
        }
      /* Direction defaults to input, but may get overridden by -o
       * on the command line.
       */
      start = 2;
      dir = INPUT;
      if (argc > 2)
        {
          if (strcmp (argv[start], "-o") == 0)
            {
              dir = OUTPUT;
              start++;
            }
          else if (strcmp (argv[start], "-i") == 0)
            {
              dir = INPUT;
              start++;
            }
        }
      /* Convert remaining command line arguments into channel
       * numbers and values. Add those to the appropriate array.
       */
      chac = 0;
      for (i = start; i < argc; i++)
        {
          if (dir == INPUT)
            {
              /* Input case, parse channel numbers only.
               */
              chav[chac] = atoi (argv[i]);
            }
          else
            {
              char *delim;
              /* Output case, parse (optional) channel number
               * and value to output.
               */
              delim = strchr (argv[i], ',');
              if (delim)
                {
                  chav[chac] = atoi (argv[i]);
                  datav[chac] = atoi (delim+1);
                }
              else
                {
                  chav[chac] = 1;
                  datav[chac] = atoi (argv[i]);
                }
            }
          chac++;
          if (chac >= 16)
            break;
        }
      /* Set/get digital i/o and print results
       * to the console.
       */
      do_digital_io (name, dir, chac, chav, datav);
      if (strcmp (argv[1], "-") == 0)
        {
          printf ("press return to continue...\n");
          fgets (tmp, sizeof(tmp), stdin);
        }
      return 0;
    }
  else
    {
      usage ();
      return 1;
    }
}
// Libad Digital I/O Example
//
// Example showing how to set and get the state of some digital inputs
// using bmcm's LIBAD4.
using System;
using LIBAD4;
static class Example
{
  // Line Direction Masks
  const uint OUTPUT = 0x0000;
  const uint INPUT  = 0xffff;
  // Get/Set Digital input(s)/output(s).
  static void
  do_digital_io (string driver, uint dir, int[] chav, uint[] datav)
  {
    // Open DAQ system.
    int adh = LIBAD.ad_open (driver);
    if (adh == -1)
      {
        Console.WriteLine ("failed to open {0}: err = {1}", driver, LIBAD.errno);
        return;
      }
    // Set Set/get digital i/o.
    for (int i = 0; i < chav.Length; i++)
      {
        uint mask;
        int rc;
        // Setup port's direction.
        LIBAD.ad_set_line_direction (adh, LIBAD.AD_CHA_TYPE_DIGITAL_IO | chav[i], dir);
        // Either read from port or write to port.
        if (dir == INPUT)
          rc = LIBAD.ad_discrete_in (adh, LIBAD.AD_CHA_TYPE_DIGITAL_IO | chav[i], 0, ref datav[i]);
        else
          rc = LIBAD.ad_discrete_out (adh, LIBAD.AD_CHA_TYPE_DIGITAL_IO | chav[i], 0, datav[i]);
        if (rc == 0)
          {
            Console.Write ("cha {0,2}: port = 0x{1,4X}, lines 16..1 =  ", chav[i], datav[i]);
            // Separate lines of that port.
            for (mask = 0x8000; mask != 0; mask >>= 1)
              {
                if ((datav[i] & mask) != 0)
                  Console.Write ("1");
                else
                  Console.Write ("0");
              }
            Console.WriteLine ();
          }
        else
          Console.WriteLine ("error: failed to write cha {0}: err = {1}", chav[i], rc);
      }
    // Close DAQ system again.
    LIBAD.ad_close (adh);
  }
  // Show usage.
  static void
  usage ()
  {
    Console.WriteLine ("usage: digital_io <driver> [-i] <cha1> .. <chan>");
    Console.WriteLine ("       digital_io <driver> -o [<cha1>,]<val1> .. [<chan>,]<valn>");
    Console.WriteLine ("  <driver>           string to pass to ad_open()");
    Console.WriteLine ("                     - will prompt for name");
    Console.WriteLine ("  <cha1> .. <chan>   number of digital port");
    Console.WriteLine ("  <val1> .. <valn>   value to set digital output to");
  }
  // Main entry point.
  static int
  Main (string[] argv)
  {
    if (argv.Length > 0)
      {
        // First command line argument is the DAQ's name.
        // If "-" is passed, then let's read the name from
        // the console.
        string name = argv[0];
        if (argv[0] == "-")
          {
            Console.Write ("data acquisition system to open: ");
            name = Console.ReadLine ();
          }
      // Direction defaults to input, but may get overridden by -o
      // on the command line.
      int start = 1;
      uint dir = INPUT;
      if (argv.Length > 1)
        {
          if (argv[start] == "-o")
            {
              dir = OUTPUT;
              start++;
            }
          else if (argv[start] == "-i")
            {
              dir = INPUT;
              start++;
            }
        }
        // Convert remaining command line arguments into channel
        // numbers and values. Add those to the appropriate array.
        int[] chav = new int[argv.Length - start];
        uint[] datav = new uint[argv.Length - start];
        for (int i = start; i < argv.Length; i++)
          {
            if (dir == INPUT)
              {
                // Input case, parse channel numbers only.
                chav[i - start] = int.Parse (argv[i]);
              }
            else
              {
                // Output case, parse (optional) channel number
                // and value to output.
                int delim = argv[i].IndexOf (',');
                if (delim >= 0)
                  {
                    chav[i - start] = int.Parse (argv[i].Substring (0, delim));
                    datav[i - start] = uint.Parse (argv[i].Substring (delim+1));
                  }
                else
                  {
                    chav[i - start] = 1;
                    datav[i - start] = uint.Parse (argv[i]);
                  }
              }
          }
        // Set/get digital i/o and print results
        // to the console.
        do_digital_io (name, dir, chav, datav);
        if (argv[0]== "-")
          {
            Console.WriteLine ("press return to continue...");
            Console.ReadLine ();
          }
        return 0;
      }
    else
      {
        usage ();
        return 1;
      }
  }
}
' Libad Digital I/O Example
'
' Example showing how to set and get the state of some digital inputs
' using bmcm's LIBAD4.
Imports System
Imports LIBAD4
Module Example
  ' Line Direction Masks
  Const OUTPUT As UInteger = &H0000
  Const INPUT  As UInteger = &Hffff
  ' Get/Set Digital input(s)/output(s).
  Sub do_digital_io (driver As String, dir As UInteger, ByVal chav As Integer(), ByVal datav As Integer())
    ' Open DAQ system.
    Dim adh As Integer
    adh = LIBAD.ad_open (driver)
    If adh = -1 Then
      Console.WriteLine ("failed to open {0}: err = {1}", driver, LIBAD.errno)
      Exit Sub
    End If
    ' Set Set/get digital i/o.
    For i = 0 To chav.Length-1
      Dim mask As Uinteger
      Dim rc As Integer
      ' Setup port's direction.
      LIBAD.ad_set_line_direction (adh, LIBAD.AD_CHA_TYPE_DIGITAL_IO or chav(i), dir)
      If dir = INPUT Then
        rc = LIBAD.ad_discrete_in (adh, LIBAD.AD_CHA_TYPE_DIGITAL_IO or chav(i), 0, datav(i))
      Else
        rc = LIBAD.ad_discrete_out (adh, LIBAD.AD_CHA_TYPE_DIGITAL_IO or chav(i), 0, datav(i))
      End If
      If rc = 0 Then
        Console.Write ("cha {0,2}: port = &H{1:X4}, lines 16..1 =  ", chav(i), datav(i))
        ' Separate lines of that port.
        mask = &H8000
        Do While mask <> 0
          If (datav(i) And mask) <> 0 Then
            Console.Write ("1")
          Else
            Console.Write ("0")
          End If
          mask = mask >> 1
        Loop
        Console.WriteLine ()
      Else
        Console.WriteLine ("error: failed to write cha {0}: err = {1}", chav(i), rc)
      End If
    Next
    ' Close DAQ system again.
    LIBAD.ad_close (adh)
  End Sub
  ' Show usage.
  Sub Usage
    Console.WriteLine ("usage: digital_io <driver> [-i] <cha1> .. <chan>")
    Console.WriteLine ("       digital_io <driver> -o [<cha1>,]<val1> .. [<chan>,]<valn>")
    Console.WriteLine ("  <driver>           string to pass to ad_open()")
    Console.WriteLine ("                     - will prompt for name")
    Console.WriteLine ("  <cha1> .. <chan>   number of digital port")
    Console.WriteLine ("  <val1> .. <valn>   value to set digital output to")
  End Sub
  ' Main entry point.
  Sub Main (ByVal argv As String())
    If argv.Length > 0 Then
      ' First command line argument is the DAQ's name.
      ' If "-" is passed, then let's read the name from
      ' the console.
      Dim name As String
      name = argv(0)
      If argv(0) = "-" Then
        Console.Write ("data acquisition sytem to open: ")
        name = Console.ReadLine ()
      End If
      ' Direction defaults to input, but may get overridden by -o
      ' on the command line.
      Dim start As Integer
      Dim dir As UInteger
      start = 1
      dir = INPUT
      If argv.Length > 1 Then
        If argv(start) = "-o" Then
          dir = OUTPUT
          start = start + 1
        Else If argv(start) = "-i" Then
          dir = INPUT
          start = start + 1
        End If
      End If
      ' Convert remaining command line arguments into channel
      ' numbers and values. Add those to the appropriate array.
      Dim chav(argv.Length-1 - start) As Integer
      Dim datav(argv.Length-1 - start) As Integer
      For i = start To argv.Length-1
        If dir = INPUT Then
          ' Input case, parse channel numbers only.
          chav(i - start) = Int32.Parse (argv(i))
        Else
          Dim delim As Integer
          delim = argv(i).IndexOf (",")
          If delim >= 0 Then
            chav(i - start) = Int32.Parse (argv(i).SubString (0, delim))
            datav(i - start) = Int32.Parse (argv(i).SubString (delim+1))
          Else
            chav(i - start) = 1
            datav(i - start) = Int32.Parse (argv(i))
          End If
        End If
      Next
      ' Set/get digital i/o and print results
      ' to the console.
      do_digital_io (name, dir, chav, datav)
      If argv(0) = "-" Then
        Console.WriteLine ("press return to continue...")
        Console.ReadLine ()
      End If
      Environment.Exit (0)
    Else
      Usage
      Environment.Exit (1)
    End If
  End Sub
End Module