hide random home http://www.be.com/documentation/be_book/device/A2D2A.html (Amiga Plus Extra No. 5/97, 05/1997)

The Device Kit: BA2D and BD2A


The Device Kit: BA2D and BD2A

Derived from: (none)

Declared in: be/device/A2D.h

Declared in: be/device/D2A.h

Library: libdevice.so


Overview

The GeekPort classes apply to BeBox hardware only.

The BA2D and BD2A classes let you talk to the GeekPort's analog-to-digital converter (ADC) and digital-to-analog converter (DAC). Before we examine the classes, let's visit the GeekPort.


The GeekPort

The GeekPort provides four channels of simultaneous analog-to-digital (a/d) and four channels of simultaneous digital-to-analog (d/a) conversion. The signals that feed the ADC arrive on pins 25-28 of the GeekPort connector; the signals that are produced by the DAC depart through pins 29-32 (as depicted below). Pins 24 and 33 are DC reference levels (ground) for the a/d and d/a signals, respectively (don't use pins 24 or 33 as power grounds):

In the illustration, the a/d and d/a pins are labelled ("A2D1", "A2D2", etc.) as they are known to the BA2D and BD2A classes.

If you've read the GeekPort hardware specification, you'll have discovered that the ADC can be placed in a few different modes (the DAC is less flexible). The BA2D and BD2A classes (more accurately, the ADC and DAC drivers) refine the GeekPort specification, as described in the following sections.

Keep in mind that the a/d and d/a converters that the GeekPort uses are not part of the Crystal codec that's used by the audio software (and brought into your application through the Media Kit). The two sets of converters are completely separate and can be used independently and simultaneously. If you're doing on-board high-fidelity sound processing (or generation) in real time, you should stick with the Crystal convertors.

The ADC

The ADC accepts signals in the range [0, +4.096] Volts, performs a linear conversion, and spits out unsigned 12-bit data. The 4.096V to 12-bit conversion produces a convenient one-digital-step per milliVolt of input.

A/d conversion is performed on-demand:

When you read a value from the ADC, the voltage that lies on the specified pin is immediately sampled (this is the "Single Shot" mode described in the GeekPort hardware specification).

In other words, the ADC doesn't perform a sample and hold--it doesn't constantly and regularly measure the voltages at its inputs. Nonetheless, you can't retrieve samples at an arbitrarily high frequency simply by reading in a tight loop. This is because of the "sampling latency":

When you ask for a sample, it takes the driver about ten microseconds to process the request, not counting the overhead imposed by the C++ call (from your BA2D object). Therefore, the fastest rate at which you can get samples from the ADC is a bit less than 100 kHz.

Furthermore, the ADC driver "fakes" the four channels of a/d conversion. In reality, there's only one ADC data path; the driver multiplexes the path to create four independent signals. This means that the optimum 100 kHz sampling frequency is divided by the number of channels that you want to read. If all four channels are being read at the same time, you'll find that successive samples on a particular channel arrive slightly less often than once every 40 microseconds (a rate of around 25 kHz).

Finally, the ADC hardware is shared by the GeekPort and the two joysticks. This cooperative use shouldn't affect your application--you can treat the ADC as if it were all your own--but it increases the multiplexing. In general, joysticks shouldn't need to sample very often, so while the theoretical "worst hit" on the ADC is a sample every 60 microseconds, the reality should be much better. If we can assume that a joystick-reading application isn't oversampling, then the BA2D "sampling latency" should stay near the 10 microseconds per channel measurement.

The DAC

The DAC accepts 8-bit unsigned data and converts it, in 16 mV steps, to an analog signal in the range [0, +4.080] Volts. Again, the quantization is linear. The DAC output isn't filtered; if you need to smooth the stair-step output, you have to build a filter into the gizmo that you're connecting to the GeekPort.

Each of the d/a pins is protected by an in-series 4.7 kOhm resistor; however, pin 33, the d/a DC reference (ground) pin, is not similarly impeded. If you want to attach an op-amp circuit to the DAC output, you should hang a 4.7 kOhm resistor on the ground pin that you're using.

Unlike the ADC, the DAC performs a sample-and-hold:

When you write a digital sample to the DAC, the specified pin is immediately set to the converted voltage. The pin continues to produce that voltage until you write another sample.

Also, the DAC is truly a four-channel device--there's no multiplexing to slow things down. Furthermore, writing to the DAC is naturally faster than writing to the ADC. You should be able to write to the DAC as frequently as you want, without worrying about a hardware-imposed "sampling latency."


BA2D

The BA2D class lets you create objects that can read the GeekPort's a/d channels. Each BA2D object can read a single channel at a time; if you want to read all four channels simultaneously, you have to create four separate objects.

To retrieve a value from one of the a/d channels, you create a new BA2D object, open it on the channel you want (using the labels shown above), and then (repeatedly) invoke the object's Read() function. When you're through reading, you call Close() so some other object can open the channel.

Reading is a one-shot deal: For each Read() invocation, you get a single ushort that stores the 12-bit ADC value in its least significant 12 bits. To get a series of successive values, you have to put the Read() call in a loop. Keep in mind that there's no sampling rate or other automatic time tethering. For example, if you want to read the ADC every tenth of a second, you have to impose the waiting period yourself (by snoozing between reads, for example).

The outline of a typical a/d-reading setup is shown below:

   #include <A2D.h>
   
   void ReadA2D1()
   {
      ushort val;
      BA2D *a2d = new BA2D();
   
      if (a2d->Open("A2D1") == B_ERROR)
         return;
   
      while ( /* whatever */ ) {
   
         /* Read() returns the number of bytes that were
          * read; a successful read returns the value 2.
          */
         if (a2d->Read(&val) != 2) 
            break;
         
         /* Apply val here. */
         ...
   
         /* Snooze for a bit. */
         snooze(1000);
      }
      a2d->Close();
      delete a2d;
   }


BD2A

Creating and using a BD2A object follows the same outline as shown above, but instead of reading a ushort value, you write a uint8. The Write() function returns 1 (the number of bytes written) if successful:

   #include <D2A.h>
   
   void WriteD2A1()
   {
      uint8 val;
      BD2A *d2a = new BD2A();
   
      if (d2a->Open("D2A1") == B_ERROR)
         return;
   
      while ( /* whatever */ ) {
         /* Get an 8-bit value from somewhere. */
         val = ...;
   
         if (d2a->Write(val) != 1) 
            break;
   
         snooze(1000);
      }
      d2a->Close();
      delete d2a;
   }

The DAC performs a sample-and-hold:

The voltage that the DAC produces on a particular channel (and to which it sets the appropriate GeekPort pin) is maintained until another Write() call (on the same channel) changes the setting.

Furthermore, the "hold" persists across all BD2A objects:

Neither closing nor deleting a BD2A object affects the voltage that's produced by the corresponding GeekPort pin.

The BD2A class also implements a Read() function. This function returns the value that was most recently written to the particular DAC channel.


Constructor and Destructor


BA2D(), BD2A()


      BA2D(void)

      BD2A(void)

Creates a new object that can open an ADC or DAC channel (respectively). The particular channel is specified in a subsequent Open() call. Constructing a new BA2D or BD2A object doesn't affect the state of the ADC or DAC.


~BA2D(), ~BD2A()


      ~BA2D(void)

      ~BD2A(void)

Closes the channel that the object holds open (if any) and then destroys the object.

Deleting a BD2A object doesn't affect the DAC channel's output voltage. If you want the voltage cleared (for example), you have to set it to 0 explicitly before deleting (or otherwise closing) the BD2A object.


Member Functions


Open(), IsOpen(), Close()


      status_t Open(const char *name)

      bool IsOpen(void)

      void Close(void)

Open() opens the named ADC (BA2D) or DAC (BD2A) channel. The channel names (as you would pass them to Open()) take the form

See the GeekPort connector illustration, above, for the correspondences between the channel names and the GeekPort connector pins.

A channel can only be held open by one object at a time; you should close the channel as soon as you're finished with it. Furthermore, each BA2D or BD2A object can only hold one channel open at a time. When you invoke Open(), the channel that the object currently has open is automatically closed--even if the channel that you're attempting to open is the channel that the object already has open.

Opening an ADC or DAC channel doesn't affect the data in the channel itself. In particular, when you open a DAC channel, the channel's output voltage isn't changed.

IsOpen() returns true if the object holds its assigned channel open channel is successfully opened. Otherwise, it returns false.

Close() does the obvious without affecting the state of the ADC or DAC channel. If you want to set a DAC channel's output voltage to 0 (for example), you must explicitly write the value before invoking Close().

RETURN CODES

The return values apply to Open() only.


Read()


      ssize_t Read(ushort *adc_12_bit)
      ssize_t Read(uint8 *dac_8_bit)

BA2D's Read() function causes the ADC to sample and convert (within a 12-bit range) the voltage level on the GeekPort pin that corresponds to the object's ADC channel. The 12-bit unsigned value is returned by reference in the adc_12_bit argument.

BD2A's Read() returns, by reference in dac_8_bit, the value that was most recently written to the object's particular DAC channel. The value needn't have been written by this object--it could have been written by the channel's previous opener.

The BD2A Read() function returns a value that's cached by the DAC driver--it doesn't actually tap the GeekPort pin to see what value it's currently carrying. This should only matter to the clever few who will attempt (unsuccessfully) to use the d/a pins as input paths.

The object must open an ADC or DAC channel before calling Read(). Note that it's not an error to read the DAC before a value has been written to it.

RETURN CODES


Write()


      ssize_t Write(uint8 dac_8_bit)

Sends the dac_8_bit value to the object's DAC channel. The DAC converts the value to an analog voltage in the range [0, +4.080] Volts and sets the corresponding GeekPort pin. The pin continues to produce the voltage until another Write() call--possibly by a different BD2A object--changes the setting.

The DAC's conversion is linear: Each digital step corresponds to 16 mV at the output. The analog voltage midpoint, +2.040V, can be approximated by a digital input of 0x7F (which produces +2.032V) or 0x80 (+2.048V).

RETURN CODES






The Be Book, in lovely HTML, for the BeOS Preview Release.

Copyright © 1997 Be, Inc. All rights reserved.

Be is a registered trademark; BeOS, BeBox, BeWare, GeekPort, the Be logo, and the BeOS logo are trademarks of Be, Inc.

Last modified June 30, 1997.