Programmer’s Guide#

Addressing a board#

Getting a board identifier#

AlazarTech organizes its digitizer boards into “board systems”. A board system is a group of one or more digitizer boards that share trigger and clock signals. To create a “board system” comprised of two or more boards, the boards need to be connected together using an AlazarTech SyncBoard. All of the channels in a board system trigger and are sampled simultaneously.

ATS-SDK assigns a “system identifier” number to each board system. The first system detected is assigned system ID number of 1. In addition, a “board identifier” number is assigned to each board in a board system. This number uniquely identifies a board within its board system.

  • If a digitizer board is not connected to any other boards using a SyncBoard, then the SDK assigns it a board ID of 1.

  • If two or more boards are connected together using a SyncBoard, then the SDK assigns each board an ID number that depends on how the board is connected to the SyncBoard. The board connected to the “master” slot on the SyncBoard is the master board in the board system and is assigned a board ID number of 1.

Call the AlazarNumOfSystems() function to determine the number of board systems detected by the SDK, and call the AlazarBoardsInSystemBySystemID() function to determine the number of boards in the board system specified by its system identifier. The following code fragment lists the system and board identifiers of each board detected by the device drivers:

U32 systemCount = AlazarNumOfSystems();
for (U32 systemId = 1; systemId <= systemCount; systemId++) {
  U32 boardCount = AlazarBoardsInSystemBySystemID(systemId);
  for (U32 boardId = 1; boardId <= boardCount; boardId++) {
    printf("Found SystemID %u Board ID = %u\\n", systemId, boardId);
  }
}

Getting a board handle#

ATS-SDK associates a handle with each digitizer board. Most functions require a board handle as a parameter. For example, the AlazarSetLED() function allows an application to control the LED on the PCI/PCIe mounting bracket of a board specified by its handle.

Use the AlazarGetBoardBySystemID() API function to get a handle to a board specified by its system identifier and board identifier numbers.

Single board installations#

If only one board is installed in a computer, ATS-SDK assigns it system ID 1 and board ID 1. The following code fragment gets a handle to such a board, and uses this handle to toggle the LED on the board’s PCI/PCIe mounting bracket:

// Select a board
U32 systemId = 1;
U32 boardId = 1;

// Get a handle to the board
HANDLE boardHandle = AlazarGetBoardBySystemID(systemId, boardId);

// Toggle the LED on the board’s PCI/PCIe mounting bracket
AlazarSetLED(boardHandle, LED_ON);
Sleep(500);
AlazarSetLED(boardHandle, LED_OFF);

Multiple board installations#

If more than one board is installed in a PC, the boards are organized into board systems, and are assigned system and board identifier numbers. The following code fragment demonstrates how to obtain a handle to each board in such an installation, and use the handle to toggle the LED on the board’s PCI/PCIe mounting bracket:

U32 systemCount = AlazarNumOfSystems();
for (U32 systemId = 1; systemId <= systemCount; systemId++) {
  U32 boardCount = AlazarBoardsInSystemBySystemID(systemId);
  for (U32 boardId = 1; boardId <= boardCount; boardId++) {
    printf("SystemID %u Board ID = %u\\n", systemId, boardId);

    // Get a handle to the board
    HANDLE handle = AlazarGetBoardBySystemID(systemId, boardId);

    // Toggle the LED on the board’s PCI/PCIe mounting bracket
    AlazarSetLED(handle, LED_ON);
    Sleep(500);
    AlazarSetLED(handle, LED_OFF);
  }
}

System handles#

Several ATS-SDK functions require a “system handle”. A system handle is the handle of the master board in a board system.

  • If a board is not connected to other boards using a SyncBoard, then its board handle is the system handle.

  • If a board is connected to other boards using a SyncBoard, then the board that is connected to the master connector on the SyncBoard is the master board, and its board handle is the system handle.

Closing a board handle#

ATS-SDK maintains a list of board handles in order to support master-slave board systems. The SDK creates board handles when it is loaded into memory, and destroys these handles when it is unloaded from memory. An application should not need to close a board handle.

Using a board handle#

ATS-SDK includes a number of functions that return information about a board specified by its handle. These functions include:

AlazarGetBoardKind()

Get a board’s model from its handle.

AlazarGetChannelInfo()

Get the number of bits per sample, and on-board memory size in samples per channel.

AlazarGetCPLDVersion()

Get the CPLD version of a board.

AlazarGetDriverVersion()

Get the driver version of a board.

AlazarGetParameter()

Get a board parameter as a signed 32-bit value.

AlazarGetParameterUL()

Get a board parameter as an unsigned 32-bit value.

AlazarQueryCapability()

Get a board capability as an unsigned 32-bit value.

The sample program “%ATS_SDK_DIR%\Samples\AlazarSysInfo” demonstrates how get a board handle and use it to obtain board properties. The API also exports functions that use a board handle to configure a board, arm it to make an acquisition, and transfer sample data from the board to application buffers. These topics are discussed in the following sections.

Resetting a board#

The ATS-SDK resets all digitizer boards during its initialization procedure. This initialization procedure automatically runs when the API library is loaded into memory.

  • If an application statically links against the API library, the API resets all boards when the application is launched.

  • If an application dynamically loads the API library, the API resets all boards when the application loads the API into memory.

Warning

Note that when an application using the API is launched, all digitizer boards are reset. If one application using the API is running when a second application using the API is launched, configuration settings written by the first application to a board may be lost. If a data transfer between the first application and a board was in progress, data corruption may occur.

Configuring a board#

Before acquiring data from a board system, an application must configure the timebase, analog inputs, and trigger system settings of each board in the board system.

Timebase#

The timebase of the ADC converters on AlazarTech digitizer boards may be supplied by:

  • Its on-board oscillators.

  • A user supplied external clock signal.

  • An on-board PLL clocked by a user supplied 10 MHz reference signal.

Internal clock#

To use on-board oscillators as a timebase, call AlazarSetCaptureClock() specifying INTERNAL_CLOCK as the clock source identifier, and select the desired sample rate with a sample rate identifier appropriate for the board. The following code fragment shows how to select a 10 MS/s internal sample rate:

AlazarSetCaptureClock(handle, // HANDLE -- board handle
                      INTERNAL_CLOCK, // U32 -- clock source Id
                      SAMPLE_RATE_10MSPS, // U32 -- sample rate Id or value
                      CLOCK_EDGE_RISING, // U32 -- clock edge Id
                      0 // U32 -- decimation
                      );

See AlazarSetCaptureClock() or the board reference manual for a list of sample rate identifiers appropriate for a board.

External clock#

AlazarTech boards optionally support using a user-supplied external clock signal input to the ECLK connector on its PCI/PCIe mounting bracket to clock its ADC converters.

To use an external clock signal as a timebase, call AlazarSetCaptureClock() specifying SAMPLE_RATE_USER_DEF as the sample rate identifier, and select a clock source identifier appropriate for the board model and the external clock properties. The following code fragment shows how to configure an ATS460 to acquire at 100 MS/s with a 100 MHz external clock:

AlazarSetCaptureClock(handle, // HANDLE -- board handle
                      FAST_EXTERNAL_CLOCK, // U32 -- clock source Id
                      SAMPLE_RATE_USER_DEF, // U32 -- sample rate Id or value
                      CLOCK_EDGE_RISING, // U32 -- clock edge Id
                      0 // U32 -- decimation
                      );

See the board reference manual for the properties of an external clock signal that are appropriate for a board, and AlazarSetCaptureClock() for a list of external clock source identifiers.

External clock level#

Some boards allow adjusting the comparator level of the external clock input receiver to match the receiver to the clock signal supplied to the ECLK connector. If necessary, call AlazarSetExternalClockLevel() to set the relative external clock input receiver comparator level, in percent.

AlazarSetExternalClockLevel(

handle, // HANDLE –- board handle level_percent, // float –- external clock level in percent );

10 MHz PLL#

Some boards can generate a timebase from an on-board PLL clocked by user supplied external 10 MHz reference signal input to its ECLK connector.

ATS660#

In 10 MHz PLL external clock mode, the ATS660 can generate a sample clock between 110 and 130 MHz, in 1 MHz, steps from an external 10 MHz reference input. Call AlazarSetCaptureClock() specifying EXTERNAL_CLOCK_10MHZ_REF as the clock source identifier, the desired sample rate between 110 and 130 MHz in 1 MHz steps, and a decimation factor of 1 to 100000. Note that the decimation value should be one less than the desired decimation factor. The following code fragment shows how to generate a 32.5 MS/s sample rate (130 MHz / 3) from a 10 MHz PLL external clock input:

AlazarSetCaptureClock(
  handle, // HANDLE –- board handle
  EXTERNAL_CLOCK_10MHZ_REF, // U32 –- clock source Id
  130000000, // U32 –- sample rate Id or value
  CLOCK_EDGE_RISING, // U32 –- clock edge Id
  2 // U32 –- decimation value
  );
ATS9325#

In 10 MHz PLL external clock mode, the ATS9325 generates a 500 MHz sample clock from an external 10 MHz reference input. The 500 MS/s sample data can be decimated by a factor of 2, 4, or any multiple of 5.

Call AlazarSetCaptureClock() specifying EXTERNAL_CLOCK_10MHZ_REF as the clock source and 500 MHz as the sample rate, and select a decimation factor of 2, 4, or any multiple of 5, up to 100000. For example, the following code fragment shows how to generate a 100 MS/s sample rate (500 MHz / 5) from a 10 MHz external clock input:

AlazarSetCaptureClock(
  handle, // HANDLE -- board handle
  EXTERNAL_CLOCK_10MHZ_REF, // U32 -- clock source Id
  500000000, // U32 -- sample rate Id
  CLOCK_EDGE_RISING, // U32 -- clock edge Id
  5 // U32 -- decimation
  );
ATS9350/ATS9351/ATS9352/ATS9353#

In 10 MHz PLL external clock mode, the ATS9350, ATS9351, ATS9352 and ATS9553 generate a 500 MHz sample clock from an external 10 MHz reference input. The 500 MS/s sample data can be decimated by a factor of 1, 2, 4, or any multiple of 5. Call AlazarSetCaptureClock() specifying EXTERNAL_CLOCK_10MHZ_REF as the clock source and 500 MHz as the sample rate, and select a decimation factor of 1, 2, 4, or any multiple of 5 up to 100000. For example, the following code fragment shows how to generate a 100 MS/s sample rate (500 MHz / 5) from a 10 MHz external clock input:

AlazarSetCaptureClock(
  handle, // HANDLE –- board handle
  EXTERNAL_CLOCK_10MHZ_REF, // U32 –- clock source Id
  500000000, // U32 –- sample rate Id
  CLOCK_EDGE_RISING, // U32 –- clock edge Id
  5 // U32 –- decimation
  );
ATS9360#

In 10 MHz PLL external clock mode, the ATS9360 can generate any sample clock frequency between 300 MHz and 1800 MHz that is a multiple of 1 MHz. Call AlazarSetCaptureClock() specifying EXTERNAL_CLOCK_10MHZ_REF as the clock source identifier, the desired sample rate between 300 MS/s and 1800 MS/s, and 1 as the decimation ratio. The sample rate must be a multiple of 1 MHz. For example, the following code fragment shows how to generate a 1.382 GS/s sample clock from a 10 MHz reference:

AlazarSetCaptureClock(
  handle, // HANDLE –- board handle
  EXTERNAL_CLOCK_10MHZ_REF, // U32 –- clock source Id
  1382000000, // U32 –- sample rate
  CLOCK_EDGE_RISING, // U32 –- clock edge Id
  1 // U32 –- decimation
  );
ATS9371#

In 10 MHz PLL external clock mode, the ATS9371 can generate any sample clock frequency between 300 MHz and 1000 MHz that is a multiple of 1 MHz. Call AlazarSetCaptureClock() specifying EXTERNAL_CLOCK_10MHZ_REF as the clock source identifier, the desired sample rate between 300 MS/s and 1000 MS/s, and 1 as the decimation ratio. The sample rate must be a multiple of 1 MHz. For example, the following code fragment shows how to generate a 882 MS/s sample clock from a 10 MHz reference:

AlazarSetCaptureClock(
  handle, // HANDLE –- board handle
  EXTERNAL_CLOCK_10MHZ_REF, // U32 –- clock source Id
  882000000, // U32 –- sample rate
  CLOCK_EDGE_RISING, // U32 –- clock edge Id
  1 // U32 –- decimation
  );
ATS9373#

In 10 MHz PLL external clock mode, the ATS9373 can generate any sample clock frequency between 500 MHz and 2000 MHz that is a multiple of 1 MHz in either single or dual channel mode. In addition, it can generate any sample clock frequency between 2000 MHz and 4000 MHz that is a multiple of 2 MHz in single channel mode.

Call AlazarSetCaptureClock() specifying EXTERNAL_CLOCK_10MHZ_REF as the clock source identifier, the desired sample rate between 300 MS/s and 4000 MS/s, and 1 as the decimation ratio. The sample rate must be a multiple of 1 MHz in dual channel if the frequency is less than or equal to 2000 MHz, and a multiple of 2 MHz if the frequency is above 2000 MHz. For example, the following code fragment shows how to generate a 1.382 GS/s sample clock from a 10 MHz reference:

AlazarSetCaptureClock(
  handle, // HANDLE –- board handle
  EXTERNAL_CLOCK_10MHZ_REF, // U32 –- clock source Id
  1382000000, // U32 –- sample rate
  CLOCK_EDGE_RISING, // U32 –- clock edge Id
  1 // U32 –- decimation
  );
ATS9440#

In 10 MHz PLL external clock mode, the ATS9440 can generate either a 125 MHz or 100 MHz sample clock from an external 10 MHz reference input. The 125 MS/s or 100 MS/s sample data can be decimated by a factor of 2, 4, or any multiple of 5.

Call AlazarSetCaptureClock() specifying EXTERNAL_CLOCK_10MHZ_REF as the clock source either 125 MHz or 100 MHz as the sample rate, and select a decimation radio between 1 and 100000. For example, the following code fragment shows how to generate a 25 MS/s sample rate (125 MHz / 5) from a 10 MHz external clock input:

AlazarSetCaptureClock(
  handle, // HANDLE –- board handle
  EXTERNAL_CLOCK_10MHZ_REF, // U32 –- clock source Id
  125000000, // U32 –- sample rate Id
  CLOCK_EDGE_RISING, // U32 –- clock edge Id
  5 // U32 –- decimation
  );
ATS9462#

In 10 MHz PLL external clock mode, the ATS9462 can generate a sample clock between 150 and 180 MHz in 1 MHz steps from an external 10 MHz reference input. Sample data can be decimated by a factor of 1 to 100000.

Call AlazarSetCaptureClock() specifying EXTERNAL_CLOCK_10MHZ_REF as the clock source, the desired sample rate between 150 and 180 MHz in 1 MHz steps, and the decimation factor of 1 to 100000. Note that the decimation value should be one less than the desired decimation factor. For example, the following code fragment shows how to generate a 15 MS/s sample rate (150 MHz / 10) from a 10 MHz external clock input:

AlazarSetCaptureClock(
  handle, // HANDLE –- board handle
  EXTERNAL_CLOCK_10MHZ_REF, // U32 –- clock source Id
  150000000, // U32 –- sample rate Id or value
  CLOCK_EDGE_RISING, // U32 –- clock edge Id
  9 // U32 –- decimation value
  );
ATS9625/ATS9626#

In 10 MHz PLL external clock mode, the ATS9625/ATS9626 can generate a 250 MHz sample clock from an external 10 MHz reference input. Sample data can be decimated by a factor of 1 to 100000.

Call AlazarSetCaptureClock() specifying EXTERNAL_CLOCK_10MHZ_REF as the clock source, 250 MHz has the sample rate value, and a decimation ratio of 1 to 100000. For example, the following code fragment shows how to generate a 25 MS/s sample rate (250 MHz / 10) from a 10 MHz external clock input:

AlazarSetCaptureClock(
  handle, // HANDLE –- board handle
  EXTERNAL_CLOCK_10MHZ_REF, // U32 –- clock source Id
  250000000, // U32 –- sample rate Id or value
  CLOCK_EDGE_RISING, // U32 –- clock edge Id
  10 // U32 –- decimation value
  );
ATS9850#

In 10 MHz PLL external clock mode, the ATS9850 generates a 500 MHz sample clock from an external 10 MHz reference input. The 500 MS/s sample data can be decimated by a factor of 1, 2, 4, or any multiple of 10.

Call AlazarSetCaptureClock() specifying EXTERNAL_CLOCK_10MHZ_REF as the clock source and 500 MHz as the sample rate value, and a decimation of 1, 2, 4, or any multiple of 10 up to 100000. For example, the following code fragment shows how to generate a 125 MS/s sample rate (500 MHz / 4) from a 10 MHz external clock input:

AlazarSetCaptureClock(
  handle, // HANDLE –- board handle
  EXTERNAL_CLOCK_10MHZ_REF, // U32 –- clock source Id
  500000000, // U32 –- sample rate Id or value
  CLOCK_EDGE_RISING, // U32 –- clock edge Id
  4 // U32 –- decimation value
  );
ATS9870#

In 10 MHz PLL external clock mode, the ATS9870 generates a 1 GHz sample clock from an external 10 MHz reference input. The 1 GS/s sample data can be decimated by a factor of 1, 2, 4, or any multiple of 10.

Call AlazarSetCaptureClock() specifying EXTERNAL_CLOCK_10MHZ_REF as the clock source and 1 GHz as the sample rate value, and a decimation of 1, 2, 4, or any multiple of 10 up to 100000. For example, the following code fragment shows how to generate a 250 MS/s sample rate (1 GHz / 4) from a 10 MHz external clock input:

AlazarSetCaptureClock(
  handle, // HANDLE –- board handle
  EXTERNAL_CLOCK_10MHZ_REF, // U32 –- clock source Id
  1000000000, // U32 –- sample rate Id or value
  CLOCK_EDGE_RISING, // U32 –- clock edge Id
  4 // U32 –- decimation value
  );

Input control#

AlazarTech digitizers have analog amplifier sections that process the signals input to its analog input connectors before they are sampled by its ADC converters. The gain, coupling, and termination of the amplifier sections should be configured to match the properties of the input signals.

Input range, coupling, and impedance#

Call AlazarInputControl() to specify the desired input range, termination, and coupling of an input channel. The following code fragment configures input CH A for a range of ±800 mV, DC coupling, and 50Ω termination:

AlazarInputControl(
  boardHandle, // HANDLE -- board handle
  CHANNEL_A, // U8 -- input channel
  DC_COUPLING, // U32 -- input coupling id
  INPUT_RANGE_PM_800_MV, // U32 -- input range id
  IMPEDANCE_50_OHM // U32 -- input impedance id
  );

See AlazarInputControl() and the board reference manual for a list of input range, coupling, and impedance identifiers appropriate for the board.

Bandwidth filter#

Some digitizers have a low pass filters that attenuate signals above about 20 MHz. By default, these filters are disabled. Call AlazarSetBWLimit() to enable or disable the bandwidth limit filter. The following code fragment enables the CH A bandwidth limit filter:

AlazarSetBWLimit (
  boardHandle, // HANDLE -- board handle
  CHANNEL_A, // U32 -- channel identifier
  1 // U32 -- 0 = disable, 1 = enable
  );

Amplifier bypass#

Some digitizer models support “amplifier bypass” mode. In this mode, the analog signal supplied to an input connector is connected directly the ADC driver of that channel, bypassing its amplifier section. Amplifier bypass mode must be enabled in hardware either through DIP switches on the board, or as a factory option. Once enabled in hardware, the following code fragment shows how to configure this option in software:

AlazarInputControl(
  handle, // HANDLE -- board handle
  CHANNEL_A, // U8 -- input channel
  DC_COUPLING, // U32 –- not used
  INPUT_RANGE_HI_FI, // U32 -- input range id
  IMPEDANCE_50_OHM // U32 –- not used
  );

Note that when amplifier bypass mode option is enabled for an input channel, the channel’s full-scale input range is fixed. The following table lists the nominal full-scale input range values that may be used to convert sample code values to volts.

Model

Full scale input range

ATS460

± 525 mV

ATS660

± 550 mV

ATS9325/ATS9350

± 200 mV

ATS9351

± 400 mV

ATS9462

± 550 mV

ATS9850/ATS9870

± 256 mV

See your board’s hardware reference manual for more information about using amplifier bypass.

Trigger control#

AlazarTech digitizer boards have a flexible triggering system with two separate trigger engines that can be used independently, or combined together to generate trigger events.

Warning

As opposed to what earlier documentation mentioned, the only way to combine trigger events is with the OR operator.

AlazarSetTriggerOperation#

Use the AlazarSetTriggerOperation() API function to configure each of the two trigger engines, and to specify how they should be used to make the board trigger:

RETURN_CODE
AlazarSetTriggerOperation (
  HANDLE handle,
  U32 TriggerOperation,
  U32 TriggerEngineId1,
  U32 SourceId1,
  U32 SlopeId1,
  U32 Level1,
  U32 TriggerEngineId2,
  U32 SourceId2,
  U32 SlopeId2,
  U32 Level2
  );

The following paragraphs describe each of the function’s parameters, and provide examples showing how to use the function.

Trigger engine#

The trigger engine identifier parameter specifies which of the two trigger engines you wish to configure. The parameter may have one of the following values:

TRIG_ENGINE_J

Configure trigger engine J

TRIG_ENGINE_K

Configure trigger engine K

Data source#

The data source identifier parameter selects the where the specified trigger engine should get its data. Refer to the documentation of the AlazarSetTriggerOperation() function for a list of all possible values.

Trigger slope#

The trigger slope identifier parameter selects whether rising or falling edges of the trigger source are detected as trigger events.

TRIGGER_SLOPE_POSITIVE

The trigger engine detects a trigger event when sample values from the trigger source rise above a specified level.

TRIGGER_SLOPE_NEGATIVE

The trigger engine detects a trigger event when sample values from the trigger source fall below a specified level.

Trigger level#

The trigger level parameter sets the level that the trigger source must rise above, or fall below, for the selected trigger engine to become active. The trigger level is specified as an unsigned 8-bit code that represents a fraction of the full-scale input range of the trigger source; 0 represents the negative full-scale input, 128 represents a 0-volt input, and 255 represents the positive full-scale input. For example, if the trigger source is CH A, and the CH A input range is ± 800 mV, then 0 represents a –800 mV trigger level, 128 represents a 0 V trigger level, and 255 represents +800 mV trigger level.

In general, the trigger level value is given by:

TriggerLevelCode = 128 + 127 * TriggerLevelVolts / InputRangeVolts.

The following table gives examples of how trigger level codes map to trigger levels in volts according to the full-scale input range of the trigger source.

Code

Fraction of input range

Level with ±1 V range

Level with ±5 V range

0

-100%

-1V

-5V

64

-50%

-500 mV

-2.5 V

96

-25%

-250 mV

-1.25 V

128

0%

0 V

0 V

160

+25 %

250 mV

1.25 V

192

+50%

+500 mV

+2.5 V

255

+100%

+1V

+5V

Trigger operation#

Finally, the trigger operation identifier specifies how the trigger events detected by the trigger engines are combined to make the board trigger. Possible values are:

TRIG_ENGINE_OP_J

The board triggers when trigger engine J detects a trigger event. Events detected by engine K are ignored.

TRIG_ENGINE_OP_K

The board triggers when trigger engine K detects a trigger event. Events detected by engine J are ignored.

TRIG_ENGINE_OP_J_OR_K

The board triggers when a trigger event is detected by any of trigger engines J and K.

AlazarSetTriggerOperation examples#

The following code fragment configures a board to trigger when the signal connected to CH A rises above 0V. This example only uses trigger engine J:

AlazarSetTriggerOperation(
  handle, // HANDLE -- board handle
  TRIG_ENGINE_OP_J, // U32 -- trigger operation
  TRIG_ENGINE_J, // U32 -- trigger engine id
  TRIG_CHAN_A, // U32 -- trigger source id
  TRIGGER_SLOPE_POSITIVE, // U32 -- trigger slope id
  128, // U32 -- trigger level (128 = 0V)
  TRIG_ENGINE_K, // U32 -- trigger engine id
  TRIG_DISABLE, // U32 -- trigger source id for engine K
  TRIGGER_SLOPE_POSITIVE, // U32 -- trigger slope id
  128 // U32 -- trigger level (0 – 255)
  );

The following code fragment configures a board to trigger when the signal connected to CH B rises above 500 mV, or falls below -200 mV, if CH B’s input range is ±1V. This example uses both trigger engine J and K:

double inputRange_volts = 1.; // ±1V range
double TriggerLevelJ_volts = .5; // +500 mV trigger level
U32 triggerLevelJ = (U32)(128 + 127 * triggerLevelJ_volts / inputRange_volts);
double triggerLevelK_volts = -.2; // -200 mV trigger level
U32 triggerLevelK = (U32)(128 + 127 * triggerLevelK_volts / inputRange_volts);
AlazarSetTriggerOperation(
  handle, // HANDLE -- board handle
  TRIG_ENGINE_OP_J_OR_K, // U32 -- trigger operation
  TRIG_ENGINE_J, // U32 -- trigger engine id
  TRIG_CHAN_B, // U32 -- trigger source id
  TRIGGER_SLOPE_POSITIVE, // U32 -- trigger slope id
  triggerLevelJ, // U32 -- trigger level from 0 to 255
  TRIG_ENGINE_K, // U32 -- trigger engine id
  TRIG_CHAN_B, // U32 -- trigger source id for engine K
  TRIGGER_SLOPE_NEGATIVE, // U32 -- trigger slope id
  triggerLevelK, // U32 -- trigger level from 0 to 255
  );

External trigger#

AlazarTech digitizer boards can trigger on a signal connected to its TRIG IN connector. To use an external trigger input:

The following code fragment configures a board to trigger when the signal connected to the TRIG IN falls below +2 V, assuming the signal’s range is less than ± 5V with DC coupling:

// Calculate the trigger level code from the level and range
double triggerLevel_volts = 2.; // trigger level
double triggerRange_volts = 5.; // input range
U32 triggerLevel_code =
(U32)(128 + 127 * triggerLevel_volts / triggerRange_volts);

// Configure trigger engine J to generate a trigger event
// on the falling edge of an external trigger signal.
AlazarSetTriggerOperation(
  handle, // HANDLE -- board handle
  TRIG_ENGINE_OP_J, // U32 -- trigger operation
  TRIG_ENGINE_J, // U32 -- trigger engine id
  TRIG_EXTERNAL, // U32 -- trigger source id
  TRIGGER_SLOPE_NEGATIVE, // U32 -- trigger slope id
  triggerLevel, // U32 -- trigger level (0 – 255)
  TRIG_ENGINE_K, // U32 -- trigger engine id
  TRIG_DISABLE, // U32 -- trigger source id for engine K
  TRIGGER_SLOPE_POSITIVE, // U32 -- trigger slope id
  128 // U32 -- trigger level (0 – 255)
  );

// Configure the external trigger input to +/-5V range,
// with DC coupling
AlazarSetExternalTrigger(
  handle, // HANDLE -- board handle
  DC_COUPLING, // U32 -- coupling id
  ETR_5V // U32 -- external range id
  );

Trigger timeout#

AlazarTech digitizer boards can be configured to automatically trigger when the board is waiting for a trigger event, but no trigger events arrive after a specified time interval. This behavior is similar to the “automatic” trigger mode of oscilloscopes, and may be useful to capture waveforms when trigger conditions are unknown. Call AlazarSetTriggerTimeOut() to specify the amount of time that a board should wait for a hardware trigger event before automatically generating a software trigger event and, as a result, acquiring one record.

Note

The trigger timeout value should be set to zero once stable trigger parameters have been found. Otherwise, a board may generate unexpected trigger events if the trigger timeout interval expires before a hardware trigger event occurs.

The following code fragment configures a board to automatically trigger and acquire one record if it does not receive a trigger event after some time:

AlazarSetTriggerTimeOut(
  boardHandle, // HANDLE -- board handle
  1000         // U32    -- Timeout in ticks
  );

The following code fragment configures a board to wait forever for trigger events:

AlazarSetTriggerTimeOut(
  boardHandle, // HANDLE -- board handle
  0            // U32    -- timeout in ticks
  );

Trigger delay#

An AlazarTech digitizer board can be configured to wait for a specified amount of time after it receives a trigger event before capturing a record for the trigger. Call AlazarSetTriggerDelay() to specify a time, in sample clock periods, to wait after receiving a trigger event for a record before capturing samples for that record. The following code fragment shows how to set a trigger delay of 1 ms, given a sample rate of 100 MS/s:

double triggerDelay_sec = 1.e-3; // 1 ms
double samplesPerSec = 100.e6; // 100 MS/s
U32 triggerDelay_samples =
(U32)(triggerDelay_sec * samplesPerSec + 0.5);
AlazarSetTriggerDelay(
  boardHandle, // HANDLE -- board handle
  triggerDelay_samples // U32 -- trigger delay in samples
  );

AUX I/O#

AlazarTech digitizer boards with an AUX I/O connector can be configured to supply a 5V TTL-level output signal, or to receive a TTL-level input signal on this connector. Use AlazarConfigureAuxIO() to configure the function of the AUX I/O connector.

The ATS9440 has two AUX I/O connectors: AUX I/O 1 and AUX I/O 2. AUX I/O 1 is configured by firmware as a trigger output signal, while AUX I/O 2 is configured by software using AlazarConfigureAuxIO(). A custom FPGA is required to change the operation of AUX I/O 1.

The ATS9625 and ATS9626 have two AUX I/O connectors: AUX1 and AUX2. AUX1 is configured by software using AlazarConfigureAuxIO(), while AUX2 is configured by the main FPGA as a trigger output signal by default. AUX2 can be controlled by its user-programmable FPGA as desired by the FPGA designer.

Trigger output#

The AUX I/O connector can be configured to supply a trigger output signal, where the edge of the trigger output signal is synchronized with the edge of the sample clock. Note that this is the default power-on mode for the AUX I/O connector. The following code fragment configures the AUX I/O connector as a trigger output signal:

AlazarConfigureAuxIO(
  handle, // HANDLE -- board handle
  AUX_OUT_TRIGGER, // U32 -- mode
  0 // U32 -- parameter
  );

Pacer output#

The AUX I/O connector can be configured to output the sample clock divided by a programmable value. This option may be used to generate a clock signal synchronized with the sample clock of the digitizer board. The following code fragment generates a 10 MHz signal on an AUX I/O connector, given a sample rate of 180 MS/s:

AlazarConfigureAuxIO(
  handle, // HANDLE -- board handle
  AUX_OUT_PACER, // U32 -- mode
  18 // U32 –- sample clock divider
  );

Note that the sample rate divider value must be greater than 2, and that the signal output may be limited by the bandwidth of the output’s TTL drivers.

Digital output#

The AUX I/O connector can be configured to output a TTL high or low signal. This mode allows a programmer to use the AUX I/O connector as a general purpose digital output. The following code fragment configures the AUX I/O connector as a digital output:

AlazarConfigureAuxIO(
  handle, // HANDLE -- board handle
  AUX_OUT_SERIAL_DATA, // U32 -- mode
  0 // U32 –- 0 = low, 1 = high
  );

Trigger enable input#

The AUX I/O connector can be configured as an AutoDMA trigger enable input signal. When enabled, a board will:

  • Wait for a rising or falling edge on the AUX I/O.

  • Wait for the number of trigger events necessary to capture the number of “records per buffer” in one AutoDMA segment specified at the start of the acquisition.

  • Repeat.

The following code fragment configures the AUX I/O connector to acquire “records per buffer” records after it receives the rising edge of a TTL pulse connected on the AUX I/O connector:

AlazarConfigureAuxIO(
  handle, // HANDLE -- board handle
  AUX_IN_TRIGGER_ENABLE, // U32 -- mode
  TRIGGER_SLOPE_POSITIVE // U32 -- parameter
  );

See Scanning Applications for more information.

Digital input#

The AUX I/O connector can be configured to read the TTL level of a signal input to the AUX connector. This mode allows a programmer to use the AUX I/O connector as a general-purpose digital input. The following code fragment configures the AUX I/O connector as a digital input:

AlazarConfigureAuxIO(
  handle, // HANDLE -- board handle
  AUX_IN_AUXILIARY, // U32 -- mode
  0 // U32 –- not used
  );

Once configured as a serial input, the following code fragment reads the AUX input level:

long level;
AlazarGetParameter(
  handle, // HANDLE -- board handle
  0, // U8 -- channel
  GET_AUX_INPUT_LEVEL, // U32 -- parameter
  &level // long* –- 0 = low, 1 = high
  );

Data packing#

By default, all the boards that have more than 8-bit per sample sampling transfer data to the host computer with 2 bytes (16 bit) per sample. This behavior can be changed on some boards by packing the data, either to 8- or 12-bits per sample. This is done by calling the AlazarSetParameter function with the PACK_MODE parameter and a packing option (either PACK_DEFAULT, PACK_8_BITS_PER_SAMPLE or PACK_12_BITS_PER_SAMPLE). The parameter must be set before calling AlazarBeforeAsyncRead.

For a list of boards that implement 8-bit packing, 12-bit packing and both; please refer to Table 9 – Miscellaneous Features Support.

Dual edge sampling#

Some AlazarTech digitizers are capable of dual edge sampling (DES), meaning that sample data is acquired both at the rising and falling edge of the clock signal. This mode can apply both to internal and external clocks. For example, ATS9373 is capable of 2 GS/s sampling in non-DES mode, and 4 GS/s in DES mode. When using the internal clock, DES sampling is activated automatically. Data must be acquired from channel A only. To use DES sampling in external clock mode, one must call AlazarSetParameter as follows before calling AlazarSetCaptureClock():

AlazarSetParameterUL(
  handle, // HANDLE -- board handle
  channelMask, // U8 -- channel to acquire
  SET_ADC_MODE,
  ADC_MODE_DES
  );

Programs that wish to use DES-capable digitizers in non-DES mode (i.e. ATS9373 at sampling frequencies at or below 2GS/s) do not need to be modified.

NPT footers#

Footers can be included to the data and contain additional information about the acquisition of each record. The footers include a timestamp, the record number in the current acquisition, a frame count and the state of the AUX I/O signal at the time of the acquisition. As the name implies, this option is only available in NPT acquisition mode.

Depending if on-FPGA FFT is used or not, the function to retrieve the NPT footers and their position in memory is different. If FFT is not enabled, NPT footers will replace the last 16 bytes of a record, leading to a loss of a few data points. These NPT footers are labeled Time-Domain to highlighting the fact that FFT is not used. When one channel is enabled, the last 8 samples of the data will be removed. When two channels are enabled, only one footer will be appended per record and will take the place of the last 4 samples from each channel.

When using on-FPGA FFT, a 128-byte word will be appended to each record. The last 16 bytes of this 128-byte word contain the footer.

Here is how to enable NPT footers:

The format of NPT footers varies a lot depending on digitizer model, acquisition configuration, etc. To extract the NPT footers, a separate open-source library should be used. This external library is available at https://github.com/alazartech/ats-footers.

Acquiring data#

AlazarTech digitizers may be configured to acquire in one of the following modes:

  • Dual port AutoDMA acquisition mode acquires to on-board memory while, at the same time, transferring data from on-board memory to application buffers.

  • Single port acquisition mode acquires data to on-board memory and then, after the acquisition is complete, transfers data from on-board memory to application buffers.

Note

Dual-port AutoDMA is the recommended acquisition mode for data acquisition with AlazarTech digitizers, because it offers much better performance and flexibility. Single-port acquisitions should only be used with PCI bus digitizers that do not have dual-port memory (i.e. ATS460 and ATS860 without dual-port memory upgrade, ATS310, ATS330, ATS850).

Dual port AutoDMA acquisition#

AutoDMA allows a board to capture sample data to on-board dual-port memory while – at the same time – transferring sample data from on-board dual-port memory to a buffer in host memory. Data acquisition and data transfer are done in parallel, so any trigger events that occur while the board is transferring data will not be missed.

AutoDMA may be used if:

  • A board has dual-port or FIFO on-board memory.

  • An application acquires at an average rate, in MB/s, that is less than maximum transfer rate of your board’s PCI or PCIe host bus interface.

AutoDMA must be used if:

  • A board has FIFO on-board memory.

  • An application cannot miss trigger events that occur while it transfers data to host memory, or re-arms for another acquisition.

  • An application acquires more sample points or records than can be stored in on-board memory.

Applications such as ultrasonic testing, OCT, radar, and imaging should use AutoDMA. An AutoDMA acquisition is divided into segments. AutoDMA hardware on a board transfers sample data, one segment at a time, from on-board memory to a buffer in host memory. There may be an unlimited number of segments in an AutoDMA acquisition, so a board can be armed to make an acquisition of infinite duration. There are four AutoDMA operating modes:

Traditional AutoDMA

This mode acquires multiple records, one per trigger event. Each record may contain samples before and after its trigger event. Each buffer contains one or more records. A record header may optionally precede each record. Supports low trigger repeat rates.

NPT AutoDMA

Acquires multiple records, one per trigger event. Some boards support a very limited number of pre-trigger samples. Otherwise, only post-trigger samples are possible. Each buffer contains one or more record. Supports high trigger repetition rates.

Triggered streaming AutoDMA

Acquires a single, gapless record spanning one or more DMA buffers. Each DMA buffer then contains only a segment of the record. This mode waits for a trigger event before acquiring the record.

Continuous streaming AutoDMA

Acquires a single, gapless record spanning one or more DMA buffers. Each DMA buffer then contains only a segment of the record. This mode does not wait for a trigger event before acquiring the record.

To make an AutoDMA acquisition, an application must:

  • Specify the AutoDMA mode, samples per record, records per buffer, and records per acquisition.

  • Arm the board to start the acquisition.

  • Wait for an AutoDMA buffer to be filled, process the buffer, and repeat until the acquisition is complete.

Note

An additional acquisition mode called Synchronous AutoDMA was available in addition to the modes presented here in former versions of the SDK. Support for this API was removed with ATSApi version 6.0.0. Refer to Annex 1 for more information.

Traditional AutoDMA#

Use traditional mode to acquire multiple records – one per trigger event – with sample points after, and optionally before, the trigger event in each record. A record header may optionally precede each record in the AutoDMA buffer. The programmer specifies the number of samples per record, records per buffer, and buffers in the acquisition. Traditional AutoDMA supports low trigger repeat rates. For high trigger repeat rates, use NPT AutoDMA mode. Digitizers with four analog input channels do not support 3-channel operation, and require sample interleave to allow for high transfer rates from on-board memory.

Each buffer is organized in memory as follows if a board has on-board memory. Rxy represents a contiguous array of samples from record x of channel y.

Enabled channels

Buffer organization

CH A

R1A, R2A, R3A, … RnA

CH B

R1B, R2B, R3B … RnB

CH A and CH B

R1A, R1B, R2A, R2B, R3A, R3B … RnA, RnB

CH C

R1C, R2C, R3C … RnC

CH A and CH C

R1A, R1C, R2A, R2C, R3A, R3C … RnA, RnC

CH B and CH C

R1B, R1C, R2B, R2C, R3B, R3C … RnB, RnC

CH D

R1D, R2D, R3D … RnD

CH A and CH D

R1A, R1D, R2A, R2D, R3A, R3D … RnA, RnD

CH B and CH D

R1B, R1D, R2B, R2D, R3B, R3D … RnB, RnD

CH C and CH D

R1C, R1D, R2C, R2D, R3C, R3D … RnC, RnD

CH A, CH B, CH C and CH D

R1A, R1B, R1C, R1D, R2A, R2B, R2C, R2D, R3A, R3B, R3C, R3D … RnA, RnB, RnC, RnD

Each buffer is organized in memory as follows if a board does not have on-board memory, or if sample interleave is enabled. Rxy represents a contiguous array of samples from record x of channel y, Rx[uv] represents interleaved samples from record x of channels u and v, and Rx[uvyz] represents interleaved samples from channels u, v, y, and z.

Enabled channels

Buffer organization

CH A

R1A, R2A, R3A, … RnA

CH B

R1B, R2B, R3B … RnB

CH A and CH B

R1[ABAB…], R2[ABAB…], … Rn[ABAB…]

CH C

R1C, R2C, R3C … RnC

CH A and CH C

R1[ACAC…], R2[ACAC…], … Rn[ACAC…]

CH B and CH C

R1[BCBC…], R2[BCBC…], … Rn[BCBC…]

CH D

R1D R2D, R3D … RnD

CH A and CH D

R1[ADAD…], R2[ADAD…], … Rn[ADAD…]

CH B and CH D

R1[BDBD…], R2[BDBD…], … Rn[BDBD…]

CH C and CH D

R1[CDCD…], R2[CDCD…], … Rn[CDCD…]

CH A, CH B, CH C and CH C

R1[ABCDABDC …], R2[ABDCABDC …], … Rn[ABDCABDC…]

Note

Some digitizer boards can be configured with the parallel DMA buffer data transfer scheme. When this feature is active, each DMA buffer contains data associated with a single input channel. The first buffer acquired contains exclusively data from channel A, the second contains data from channel B, etc. This feature is only available for multi-channel acquisitions on specific digitizers.

See “%ATS_SDK_DIR%\Samples\DualPort\TR” for a sample program that demonstrates how to make an AutoDMA acquisition in Traditional mode.

If record headers are enabled, then a 16-byte record header will precede each record in an AutoDMA buffer. The record header contains a record timestamp, as well as acquisition metadata. See Record headers and timestamps below for a discussion of AutoDMA record headers.

NPT AutoDMA#

Use NPT mode to acquire multiple records – one per trigger event – with no sample points before the trigger event in each record, and with no record headers. The programmer specifies the number of samples per record, records per buffer, and buffers in the acquisition. Note that NPT mode is highly optimized, and supports higher trigger repeats rate than possible in Traditional mode. Digitizers with four analog input channels do not support 3-channel operation, and require sample interleave to allow for high transfer rates from on-board memory.

Each buffer is organized in memory as follows if a board has on-board memory. Rxy represents a contiguous array of samples from record x of channel y.

Enabled channels

Buffer organization

CH A

R1A, R2A, R3A, … RnA

CH B

R1B, R2B, R3B … RnB

CH A and CH B

R1A, R2A, R3A … RnA, R1B, R2B, R3B … RnB

CH C

R1C, R2C, R3C, … RnC

CH A and CH B

R1A, R2A, R3A … RnA, R1B, R2B, R3B … RnB

CH B and CH C

R1B, R2B, R3B … RnB, R1C, R2C, R3C … RnC

CH D

R1D, R2D, R3D, … RnD

CH A and CH D

R1A, R2A, R3A … RnA, R1D, R2D, R3D … RnD

CH B and CH D

R1B, R2B, R3B … RnB, R1D, R2D, R3D … RnD

CH C and CH D

R1C, R2C, R3C … RnC, R1D, R2D, R3D … RnD

CH A, CH B, CH C, and CH D

R1A, R2A, R3A … RnA, R1B, R2B, R3B … RnB, R1C, R2C, R3C … RnC, R1D, R2D, R3D … RnD

Each buffer is organized in memory as follows if a board does not have on-board memory, or if sample interleave is enabled. Rxy represents a contiguous array of samples from record x of channel y, Rx[uv] represents interleaved samples from record x of channels u and v, and Rx[uvyz] represents interleaved samples from record x of channels u, v, y, and z.

Enabled channels

Buffer organization

CH A

R1A, R2A, R3A, … RnA

CH B

R1B, R2B, R3B … RnB

CH A and CH B

R1[ABAB…], R2[ABAB…], … Rn[ABAB…]

CH C

R1C, R2C, R3C … RnC

CH A and CH C

R1[ACAC…], R2[ACAC…], … Rn[ACAC…]

CH B and CH C

R1[BCBC…], R2[BCBC…], … Rn[BCBC…]

CH D

R1D R2D, R3D … RnD

CH A and CH D

R1[ADAD…], R2[ADAD…], … Rn[ADAD…]

CH B and CH D

R1[BDBD…], R2[BDBD…], … Rn[BDBD…]

CH C and CH D

R1[CDCD…], R2[CDCD…], … Rn[CDCD…]

CH A, CH B, CH C and CH D

R1[ABCDABCD …], R2[ABCDABCD …], … Rn[ABCDABCD…]

Note

Some digitizer boards can be configured with the parallel DMA buffer data transfer scheme. When this feature is active, each DMA buffer contains data associated with a single input channel. The first buffer acquired contains exclusively data from channel A, the second contains data from channel B, etc. This feature is only available for multi-channel acquisitions on specific digitizers.

See “%ATS_SDK_DIR%\Samples\DualPort\NPT” for a sample program that demonstrates how to make an AutoDMA acquisition in NPT mode.

Continuous streaming AutoDMA#

Use continuous streaming mode to acquire a single, gapless record that spans multiple buffers without waiting for a trigger event to start the acquisition. The programmer specifies the number of samples per buffer, and buffers per acquisition. Each buffer is organized as follows if a board has on-board memory. R1x represents a contiguous array of samples from channel x.

Enabled channels

Buffer organization

CH A

R1A

CH B

R1B

CH A and CH B

R1A, R1B

CH C

R1C

CH A and CH C

R1A, R1C

CH B and CH C

R1B, R1C

CH D

R1D

CH A and CH D

R1A, R1D

CH B and CH D

R1B, R1D

CH C and CH D

R1C, R1D

CH A, CH B, CH C and CH D

R1A, R1B, R1C, R1D

Each buffer is organized as follows if a board does not have on-board memory, or if sample interleave is enabled. R1x represents a contiguous array of samples from channel x, R1[uv] represents samples interleaved from channels u and v, and R1[uvyz] represents samples interleaved from channels u, v, y, and z.

Enabled channels

Buffer organization

CH A

R1A

CH B

R1B

Both CH A and CH B

R1[ABAB…]

CH C

R1C

CH A and CH C

R1[ACAC…]

CH B and CH C

R1[BCBC…]

CH D

R1D

CH A and CH D

R1[ADAD…]

CH B and CH D

R1[BDBD…]

CH C and CH D

R1[CDCD…]

CH A, CH B, CH C and CH D

R1[ABCDABCD …]

Note

Some digitizer boards can be configured with the parallel DMA buffer data transfer scheme. When this feature is active, each DMA buffer contains data associated with a single input channel. The first buffer acquired contains exclusively data from channel A, the second contains data from channel B, etc. This feature is only available for multi-channel acquisitions on specific digitizers.

See “%ATS_SDK_DIR%\Samples\DualPort\CS” for a sample program that demonstrates how to make an AutoDMA acquisition in continuous streaming mode.

Triggered streaming AutoDMA#

Use triggered streaming mode to acquire a single, gapless record that spans two or more buffers after waiting for a trigger event to start the acquisition. The programmer specifies the number of samples in each buffer, and buffers in the acquisition. Each buffer is organized as follows if a board has on-board memory. R1x represents a contiguous array of samples from channel x.

Enabled channels

Buffer organization

CH A

R1A

CH B

R1B

CH A and CH B

R1A, R1B

CH C

R1C

CH A and CH C

R1A, R1C

CH B and CH C

R1B, R1C

CH D

R1D

CH A and CH D

R1A, R1D

CH B and CH D

R1B, R1D

CH C and CH D

R1C, R1D

CH A, CH B, CH C and CH D

R1A, R2B, R1C, R1D

Each buffer is organized as follows if a board does not have on-board memory, or if sample interleave is enabled. R1x represents a contiguous array of samples from channel x, R1[uv] represents samples interleaved from channels u and v, and R1[uvyz] represents samples interleaved from channels u, v, y, and z.

Enabled channels

Buffer organization

CH A

R1A

CH B

R1B

Both CH A and CH B

R1[ABAB…]

CH C

R1C

CH A and CH C

R1[ACAC…]

CH B and CH C

R1[BCBC…]

CH D

R1D

CH A and CH D

R1[ADAD…]

CH B and CH D

R1[BDBD…]

CH C and CH D

R1[CDCD…]

CH A, CH B, CH C and CH D

R1[ABCDABCD …]

Note

Some digitizer boards can be configured with the parallel DMA buffer data transfer scheme. When this feature is active, each DMA buffer contains data associated with a single input channel. The first buffer acquired contains exclusively data from channel A, the second contains data from channel B, etc. This feature is only available for multi-channel acquisitions on specific digitizers.

See “%ATS_SDK_DIR%\Samples\DualPort\TS” for a sample program that demonstrates how to make a triggered streaming AutoDMA acquisition.

Record headers and timestamps#

In traditional AutoDMA mode, a 16-byte record header may optionally precede each record in a buffer. When record headers are enabled, the following table shows the buffer layout if a board has on-board memory. Record headers are not supported if a board does not have on-board memory. Rxy represents a contiguous array of samples from record x of channel y, and Hxy is a 16-byte record header from record x of channel y.

Enabled channels

Buffer organization

CH A

H1A, R1A, H2A, R2A … HnA, RnA

CH B

H1B, R1B, H2B, R2B … HnB, RnB

CH A and CH B

H1A, R1A, H1B, R1B, H2A, R2A, H2B, R2B… HnA, RnA, HnB, RnB

CH C

H1C, R1C, H2C, R2C … HnC, RnC

CH A and CH C

H1A, R1A, H1C, R1C, H2A, R2A, H2C, R2C… HnA, RnA, HnC, RnC

CH B and CH C

H1B, R1B, H1C, R1C, H2B, R2B, H2C, R2C… HnB, RnB, HnC, RnC

CH D

H1D, R1D, H2D, R2D … HnD, RnD

CH A and CH D

H1A, R1A, H1D, R1D, H2A, R2A, H2D, R2D… HnA, RnA, HnD, RnD

CH B and CH D

H1B, R1B, H1D, R1D, H2B, R2B, H2D, R2D… HnB, RnB, HnD, RnD

CH C and CH D

H1C, R1C, H1D, R1D, H2C, R2C, H2D, R2D… HnC, RnC, HnD, RnD

CH A, CH B, CH C and CH D

H1A, R1A, H1B, R1B, H1C, R1C, H1D, R1D, H2A, R2A, H2B, R2B H2C, R2C, H2D, R2D… HnA, RnA, HnB, RnB, HnC, RnC, HnD, RnD

Record headers#

A record header is a 16-byte structure defined in AlazarApi.h as follows:

struct _HEADER0 {
  unsigned int SerialNumber:18; // bits 17..0
  unsigned int SystemNumber:4; // bits 21..18
  unsigned int WhichChannel:1; // bit 22
  unsigned int BoardNumber:4; // bits 26..23
  unsigned int SampleResolution:3; // bits 29..27
  unsigned int DataFormat:2; // bits 31..30
};

struct _HEADER1 {
  unsigned int RecordNumber:24; // bits 23..0
  unsigned int BoardType:8; // bits 31..24
};

struct _HEADER2 {
  U32 TimeStampLowPart; //bits 31..0
};

struct _HEADER3 {
  unsigned int TimeStampHighPart:8; // bits 7..0
  unsigned int ClockSource:2; // bits 9..8
  unsigned int ClockEdge:1; // bit 10
  unsigned int SampleRate:7; // bits 17..11
  unsigned int InputRange:5; // bits 22..18
  unsigned int InputCoupling:2; // bits 24..23
  unsigned int InputImpedance:2; // bits 26..25
  unsigned int ExternalTriggered:1; // bit 27
  unsigned int ChannelBTriggered:1; // bit 28
  unsigned int ChannelATriggered:1; // bit 29
  unsigned int TimeOutOccurred:1; // bit 30
  unsigned int ThisChannelTriggered:1; // bit 31
};

typedef struct _ALAZAR_HEADER {
  struct _HEADER0 hdr0;
  struct _HEADER1 hdr1;
  struct _HEADER2 hdr2;
  struct _HEADER3 hdr3;
} *PALAZAR_HEADER;

typedef struct _ALAZAR_HEADER ALZAR_HEADER;

See ALAZAR_HEADER for more information about each of the fields of this structure. See “%ATS_SDK_DIR%\Samples\DualPort\TR_Header” for a full sample program that demonstrates how to make an AutoDMA acquisition in Traditional mode with record headers.

Record timestamps#

AlazarTech digitizer boards include a high-speed 40-bit counter that is clocked by the sample clock source scaled by a board specific divider. When a board receives a trigger event to capture a record to on-board memory, it latches the value of this counter. This timestamp value gives the time, relative to when the counter was reset, when the trigger event for this record occurred. By default, this counter is reset to zero at the start of each acquisition. Use AlazarResetTimeStamp() to control when the record timestamp counter is reset. The following code fragment demonstrates how to extract the timestamp from a record header, and covert the value from counts to seconds:

double samplesPerTimestampCount = 2; // board specific constant
double samplesPerSec = 100.e6; // sample rate
void* pRecord; // points to record header in buffer
ALAZAR_HEADER *pHeader = (ALAZAR_HEADER*) pRecord;
__int64 timestamp_counts;
timestamp_counts = (INT64) pHeader->hdr2.TimeStampLowPart;
timestamp_counts = timestamp_counts |
(((__int64) (pHeader->hdr3.TimeStampHighPart & 0x0ff)) << 32);
double timestamp_sec = samplesPerTimestampCount *
timestamp_counts / samplesPerSec;

Call AlazarGetParameter() with the GET_SAMPLES_PER_TIMESTAMP_CLOCK parameter to determine the board specific “samples per timestamp count” value. Samples per record requirements lists these values. See “%ATS_SDK_DIR%\Samples\DualPort\TR_Header” for a full sample program that demonstrates how to make an AutoDMA acquisition in Traditional mode with record headers, and convert the timestamp to seconds.

AutoDMA acquisition flow#

The AutoDMA functions allow an application to add user-defined number of buffers to a list of buffers available to be filled by a board, and to wait for the board to receive sufficient trigger events to fill the buffers with sample data. The board uses AutoDMA to transfer data directly into a buffer without making any intermediate copies in memory. As soon as one buffer is filled, the driver automatically starts an AutoDMA transfer into the next available buffer.

AlazarPostBuffer#

C/C++ applications should call AlazarPostAsyncBuffer() to make buffers available to be filled by the board, and AlazarWaitAsyncBufferComplete() to wait for the board to receive sufficient trigger events to fill the buffers. The following code fragment outlines the steps required to make an AutoDMA acquisition using AlazarPostAsyncBuffer() and AlazarWaitAsyncBufferComplete():

// Configure the board to make an AutoDMA acquisition
AlazarBeforeAsyncRead(
  handle, // HANDLE -- board handle
  channelMask, // U32 -- enabled channel mask
  -(long)preTriggerSamples, // long -- trigger offset
  samplesPerRecord, // U32 -- samples per record
  recordsPerBuffer, // U32 -- records per buffer
  recordsPerAcquisition, // U32 -- records per acquisition
  flags // U32 -- AutoDMA mode and options
  );

// Add two or more buffers to a list of buffers
// available to be filled by the board
for (i = 0; i < BUFFER_COUNT; i++) {
  AlazarPostAsyncBuffer(
    handle, // HANDLE -- board handle
    BufferArray[i], // void* -- buffer pointer
    BytesPerBuffer // U32 -- buffer length in bytes
    );
}

// Arm the board to begin the acquisition
AlazarStartCapture(handle);

// Wait for each buffer in the acquisition to be filled
U32 buffersCompleted = 0;
while (buffersCompleted < buffersPerAcquisition) {
  // Wait for the board to receives sufficient trigger events
  // to fill the buffer at the head of its list of
  // available buffers.
  U32 bufferIndex = buffersCompleted % BUFFER_COUNT;
  U16* pBuffer = BufferArray[bufferIndex];
  AlazarWaitAsyncBufferComplete(handle, pBuffer, timeout_ms);
  buffersCompleted++;

  // The buffer is full, process it.
  // Note that while the application processes this buffer,
  // the board is filling the next available buffer
  // as trigger events arrive.
  ProcessBuffer(pBuffer, bytesPerBuffer);

  // Add the buffer to the end of the list of buffers
  // available to be filled by this board. The board will
  // fill it with another segment of the acquisition after
  // all of the buffers preceding it have been filled.
  AlazarPostAsyncBuffer(handle, pBuffer, bytesPerBuffer);
}

// Abort the acquisition and release resources.
// This function must be called after an acquisition.
AlazarAbortAsyncRead(boardHandle);

See “%ATS_SDK_DIR%\Samples\DualPort\NPT” for a full sample program that demonstrates make an AutoDMA acquisition using AlazarPostAsyncBuffer.

ADMA_ALLOC_BUFERS#

C#, and LabVIEW applications may find it more convenient to allow the API to allocate and manage a list of buffers available to be filled by the board. These applications should call AlazarBeforeAsyncRead() with the AMDA_ALLOC_BUFFERS option selected in the “Flags” parameter. This option will cause the API to allocate and manage a list of buffers available to be filled by the board. The application must call AlazarWaitNextAsyncBufferComplete() to wait for a buffer to be filled. When the board receives sufficient trigger events to fill a buffer, the API will copy the data from the internal buffer to the user-supplied buffer. The following code fragment outlines how make an AutoDMA acquisition using the ADMA_ALLOC_BUFFERS flag and AlazarWaitNextAsyncBufferComplete():

// Allow the API to allocate and manage AutoDMA buffers
flags |= ADMA_ALLOC_BUFFERS;

// Configure a board to make an AutoDMA acquisition
AlazarBeforeAsyncRead(
  handle, // HANDLE -- board handle
  channelMask, // U32 -- enabled channel mask
  -(long)preTriggerSamples, // long -- trigger offset
  samplesPerRecord, // U32 -- samples per record
  recordsPerBuffer, // U32 -- records per buffer
  recordsPerAcquisition, // U32 -- records per acquisition
  flags // U32 -- AutoDMA mode and options
  );

// Arm the board to begin the acquisition
AlazarStartCapture(handle);

// Wait for each buffer in the acquisition to be filled
RETURN_CODE retCode = ApiSuccess;
while (retCode == ApiSuccess) {
  // Wait for the board to receive sufficient
  // trigger events to fill an internal AutoDMA buffer.
  // The API will copy data from the internal buffer
  // to the user-supplied buffer.
  retCode =
  AlazarWaitNextAsyncBufferComplete(
    handle, // HANDLE -- board handle
    pBuffer, // void* -- buffer to receive data
    bytesToCopy, // U32 -- bytes to copy into buffer
    timeout_ms // U32 -- time to wait for buffer
    );

  // The buffer is full, process it
  // Note that while the application processes this buffer,
  // the board is filling the next available internal buffer
  // as trigger events arrive.
  ProcessBuffer(pBuffer, bytesPerBuffer);
}

// Abort the acquisition and release resources.
// This function must be called after an acquisition.
AlazarAbortAsyncRead(boardHandle);

See “%ATS_SDK_DIR%\Samples\DualPort\CS_WaitNextBuffer” for a full sample program that demonstrates make an AutoDMA acquisition using ADMA_ALLOC_BUFFERS. An application can get or set the number of DMA buffers allocated by the API by calling AlazarGetParameter() or AlazarSetParameter() with the parameter SETGET_ASYNC_BUFFCOUNT.

Note that applications may combine ADMA_ALLOC_BUFFERS with options to perform operations that would be difficult in high-level programming languages like LabVIEW. They include:

  • Data normalization – This option enables the API to process sample data so that the data always has the same arrangement in the application buffer, independent of AutoDMA mode. See ADMA_GET_PROCESSED_DATA for more information.

  • Disk streaming – This option allows the API to use high-performance disk I/O functions to stream buffer data to files. See AlazarCreateStreamFile() below for more information.

AlazarAsyncRead#

Some C/C++ applications under Windows may require waiting for an event to be set to the signaled state to indicate when an AutoDMA buffer is full. These applications should use the AlazarAsyncRead() API. The following code fragment outlines how use AlazarAsyncRead() to make an asynchronous AutoDMA acquisition:

// Configure the board to make an AutoDMA acquisition
AlazarBeforeAsyncRead(
  handle, // HANDLE -- board handle
  channelMask, // U32 -- enabled channel mask
  -(long)preTriggerSamples, // long -- trigger offset
  samplesPerBuffer, // U32 -- samples per buffer
  recordsPerBuffer, // U32 -- records per buffer
  recordsPerAcquisition, // U32 -- records per acquisition
  admaFlags // U32 -- AutoDMA flags
  );

// Add two or more buffers to a list of buffers
// available to be filled by the board
for (i = 0; i < BUFFER_COUNT; i++) {
  AlazarAsyncRead (
    handle, // HANDLE -- board handle
    IoBufferArray[i].buffer, // void* -- buffer
    IoBufferArray[i].bytesPerBuffer, // U32 -- buffer length
    &IoBufferArray[i].overlapped // OVERLAPPED*
    );
}

// Arm the board to begin the acquisition
AlazarStartCapture(handle);

// Wait for each buffer in the acquisition to be filled.
U32 buffersCompleted = 0;
while (buffersCompleted < buffersPerAcquisition)
{
// Wait for the board to receives sufficient
// trigger events to fill the buffer at the head of its
// list of available buffers.
// The event handle will be set to the signaled state when
// the buffer is full.
U32 bufferIndex = buffersCompleted % BUFFER_COUNT;
IO_BUFFER *pIoBuffer = IoBufferArray[bufferIndex];
WaitForSingleObject(pIoBuffer->hEvent, INFINTE);
buffersCompleted++;

// The buffer is full, process it
// Note that while the application processes this buffer,
// the board is filling the next available buffer
// as trigger events arrive.
ProcessBuffer(pIoBuffer->buffer, pIoBuffer->bytesPerBuffer);

// Add the buffer to the end of the list of buffers.
// The board will fill it with another segment from the
// acquisition after the buffers preceding it have been filled.
AlazarAsyncRead (
handle, // HANDLE -- board handle
pIoBuffer->buffer, // void* -- buffer
pIoBuffer->bytesPerBuffer, // U32 -- buffer length
&pIoBuffer->overlapped // OVERLAPPED*
);
}

// Stop the acquisition. This function must be called if unfilled buffers are
// pending.
AlazarAbortAsyncRead(handle);

See “%ATS_SDK_DIR%\Samples\DualPort\CS_AsyncRead” for a full sample program that demonstrates make an AutoDMA acquisition using AlazarAsyncRead().

AlazarAbortAsyncRead#

The asynchronous API driver locks application buffers into memory so that boards may DMA directly into them. When a buffer is completed, the driver unlocks it from memory. An application must call AlazarAbortAsyncRead() if, at the end of an acquisition, any of the buffers that it supplies to a board have not been completed. AlazarAbortAsyncRead() completes any pending buffers, and unlocks them from memory.

Warning

If an application exits without calling AlazarAbortAsyncRead(), the API driver may generate a DRIVER_LEFT_LOCKED_PAGES_IN_PROCESS (0x000000CB) bug check error under Windows, or leak the locked memory under Linux. This may happen, for example, if a programmer runs an application that uses the API under a debugger, stops at a breakpoint, and then stops the debugging session without letting the application or API exit normally.

Buffer count#

An application should supply at least two buffers to a board. This allows the board to fill one buffer while the application consumes the other. As long as the application can consume buffers faster than the board can fill them, the acquisition can continue indefinitely. However, Microsoft Windows and general-purpose Linux distributions are not real time operating systems. An application thread may be suspended for an indeterminate amount of time to allow other threads with higher priority to run. As a result, buffer processing may take longer than expected. The board is filling AutoDMA buffers with sample data in real time. If an application is unable to supply buffers as fast a board fills them, the board will run out of buffers into which it can transfer sample data. The board can continue to acquire data until it fills is on-board memory, but then it will abort the acquisition and report a buffer overflow error.

It is recommended that an application supply three or more buffers to a board. This allows some tolerance for operating system latencies. The programmer may need to increase the number of buffers according to the application.

Note

The number of buffers required by a board is not the same as the number of buffers required by an application. There may be little benefit in supplying a board with more than a few tens of buffers, each of a few million samples. If an application requires much more sample data for data analysis or other purposes, the programmer should consider managing application buffers separately from AutoDMA buffers.

Scanning applications#

Scanning applications divide an acquisition into frames, where each frame is composed of a number of scan lines, and each scan line is composed of a number of sample points. These applications typically:

  • Wait for a “start of frame” event.

  • Wait for a number of “start of line” events, capturing a specified number of sample points after each “start of line” event.

  • Wait for the next “start of frame” event and repeat.

To implement a scanning application using a hardware “start of frame” signal:

  • Connect a TTL signal that will serve as the “start of frame” event to the AUX I/O connector.

  • Call AlazarConfigureAuxIO() specifying AUX_IN_TRIGGER_ENABLE as the mode, and the active edge of the trigger enable signal as the parameter.

  • Configure the board to make an NPT() or Traditional() mode AutoDMA acquisition where the number of “records per buffer” is equal to the number of scan lines per frame.

  • Call AlazarStartCapture() to being the acquisition.

  • Supply a TTL pulse to the AUX I/O connector (or call AlazarForceTriggerEnable()) to arm the board to capture one frame. The board will wait for sufficient trigger events to capture the number of records in an AutoDMA buffer, and then wait for the next trigger enable event.

To implement a scanning application using a software “start of frame” command:

  • Call AlazarConfigureAuxIO() specifying AUX_OUT_TRIGGER_ENABLE as the mode, along with the signal to output on the AUX I/O connector.

  • Configure the board to make an NPT() or Traditional() mode AutoDMA acquisition where the number of “records per buffer” is equal to the number of scan lines per frame.

  • Call AlazarStartCapture() to begin the acquisition.

  • Call AlazarForceTriggerEnable() to arm the board to capture one frame. The board will wait for sufficient trigger events to capture the number of records in an AutoDMA buffer, and then wait for the next trigger enable event.

Note that if the number of records per acquisition is set to infinite, software arms the digitizer once to make an AutoDMA acquisition with an infinite number of frames. The hardware will continue acquiring frame data until the acquisition is aborted. See “%ATS_SDK_DIR%\Samples\DualPort\NPT_Scan” for sample programs that demonstrate how to make a scanning application using hardware trigger enable signals.

Other scanning applications (NPT Footers)#

In some other applications, an acquisition is divided several frames, but the number of records per frame is not constant. This happens in imaging applications such as intravascular OCT. The rotation speed of the imaging probe is not constant and the number of records (A-lines) may vary from one frame to the other.

For this situation, the AUX I/O connector should not be used as a trigger enable input as in conventional scanning application. Instead, it can be used a frame counter. The frame number can be appended to each data record so the user can recover the frame number for each record and then reconstruct each frame correctly. These are called footers and can only be used in NPT acquisition mode. See the NPT footers section for more details about using NPT footers.

Master-slave applications#

If a dual-port acquisition API is used to acquire from master-slave board system:

  • Call AlazarBeforeAsyncRead() on all slave boards before the master board.

  • Call AlazarStartCapture() only on the master board.

  • Call AlazarAbortAsyncRead() on the master board before the slave boards.

  • The board system acquires the boards in the board system in parallel. As a result, an application must consume a buffer from each board in the board system during each cycle of the acquisition loop.

  • Do not use synchronous API functions with master-slave systems – use the asynchronous API functions instead.

The following sample programs demonstrate how to acquire from a master-slave system: “%ATS_SDK_DIR%\Samples\DualPort\TR_MS”, “%ATS_SDK_DIR%\Samples\DualPort\NPT_MS”, “%ATS_SDK_DIR%\Samples\DualPort\CS_MS”, and “%ATS_SDK_DIR%\Samples\DualPort\TS_MS”.

Buffer size and alignment#

AlazarTech digitizer boards must be configured to acquire a minimum number of samples per record, and each record must be a multiple of a specified number of samples. Records may shift within a buffer if alignment requirements are not met. Please refer to Samples per record requirements for a list of requirements.

The number of pre-trigger samples in single-port and dual-port “traditional” AutoDMA mode must be a multiple of the pre-trigger alignment value above. See AlazarSetRecordCount() and AlazarSetRecordSize() for more information.

The address of application buffers passed to the following data transfer functions must meet the buffer alignment requirement in Samples per record requirements: AlazarRead(), AlazarReadEx(), AlazarAsyncRead(), AlazarPostAsyncBuffer(), and AlazarWaitAsyncBufferComplete(). For example, the address of a buffer passed to AlazarPostAsyncBuffer to receive data from an ATS9350 must be aligned to a 32-sample, or 64-byte, address.

Note that AlazarWaitNextAsyncBufferComplete() has no alignment requirements. As a result, an application can use this function to transfer data if it is impossible to allocate correctly aligned buffers.

Data format#

By default, AlazarTech digitizers generate unsigned sample data. For example, 8-bit digitizers such as the ATS9870 generate sample codes between 0 and 255 (0xFF) where: 0 represents a negative full-scale input voltage, 128 (0x80) represents ~0V input voltage, 255 (0xFF) represents a positive full-scale input voltage. Some AlazarTech digitizer can be configured to generate signed sample data in two’s complement format. For example, the ATS9870 can be configured to generate sample codes where: 0 represents ~0V input voltage, 127 (0x7F) represents a positive full-scale input voltage, and –128 (0x80) represents a negative full-scale input voltage.

Call AlazarSetParameter() with parameter SET_DATA_FORMAT before the start of an acquisition to set the sample data format, and call AlazarGetParameter() with GET_DATA_FORMAT to get the current data format. The following code fragment demonstrates how to select signed sample data output:

AlazarSetParameter(
  handle, // HANDLE -- board handle
  0, // U8 -- channel Id (not used)
  SET_DATA_FORMAT, // U32 -- parameter to set
  DATA_FORMAT_SIGNED // long -- value (0 = unsigned, 1 = signed)
  );

Single port acquisition#

The single-port acquisition API allows an application to capture records to on-board memory – one per trigger event – and transfer records from on-board to host memory. Data acquisition and data transfer are made serially, so trigger events may be missed if they occur during data transfers.

Note

The single port acquisition mode is not recommended for new designs. It should only be used with PCI bus digitizers that are not capable of making dual-port acquisitions: ATS460 and ATS860 without dual-port memory upgrade, ATS310, ATS330, ATS850.

Acquiring to on-board memory#

All channels mode#

By default, AlazarTech digitizer boards share on-board memory equally between both of a board’s input channels. A single-port acquisition in dual-channel mode captures samples from both input channels simultaneously to on-board memory and, after the acquisition is complete, allows samples from either input channel to be transferred from on-board memory to an application buffer. To program a board acquire to on-board memory in dual-channel mode:

  1. Call AlazarSetRecordSize() to set the number of samples per record, where a record may contain samples before and after its trigger event.

  2. Call AlazarSetRecordCount() to set the number records per acquisition – the board captures one record per trigger event.

  3. Call AlazarStartCapture() to arm the board to wait for trigger events.

  4. Call AlazarBusy() in a loop to poll until the board has received all trigger events in the acquisition, and has captured all records to on-board memory.

  5. Call AlazarRead(), AlazarReadEx(), or AlazarHyperDisp() to transfer records from on-board memory to host memory.

  6. Repeat from step 3, if necessary.

The following code fragment acquires to on board memory with on-board memory shared between both input channels:

// 1. Set record size
AlazarSetRecordSize (
  boardHandle, // HANDLE -- board handle
  preTriggerSamples, // U32 -- pre-trigger samples
  postTriggerSamples // U32 -- post-trigger samples
  );

// 2. Set record count
AlazarSetRecordCount(
  boardHandle, // HANDLE -- board handle
  recordsPerCapture // U32 -- records per acquisition
  );

// 3. Arm the board to wait for trigger events
AlazarStartCapture(boardHandle);

// 4. Wait for the board to receive all trigger events and capture all
//    records to on-board memory

while (AlazarBusy (boardHandle))
{
  // The acquisition is in progress
}

// 5. The acquisition is complete. Call AlazarRead or AlazarHyperDisp to
//    transfer records from on-board memory to your buffer.
Single channel mode#

Note

The single port acquisition mode is not recommended for new designs. It should only be used with digitizers that are not capable of making dual-port acquisitions: ATS310, ATS330 and ATS850.

ATS9325, ATS9350, ATS9351, ATS9440, ATS9625, ATS9626, ATS9850, and ATS9870 and digitizer boards can be configured to dedicate all on-board memory to one of a board’s input channels. A single-port acquisition in single-channel mode only captures samples from the specified channel to on-board memory and, after the acquisition is complete, only allows samples from the specified channel to be transferred from on-board memory to an application buffer.

To program a board to acquire to on-board memory in single-channel mode:

  1. Call AlazarSetRecordSize() to set the number of samples per record, where a record may contain samples before and after its trigger event.

  2. Call AlazarSetRecordCount() to set the number records per acquisition – the board captures one record per trigger event.

  3. Call AlazarSetParameter() with the parameter SET_SINGLE_CHANNEL_MODE, and specify the channel to use all memory.

  4. Call AlazarStartCapture() to arm the board to wait for trigger events.

  5. Call AlazarBusy() in a loop to poll until the board has received all trigger events in the acquisition, and has captured all records to on-board memory.

  6. Call AlazarRead(), AlazarReadEx(), or AlazarHyperDisp() to transfer records from on-board memory to host memory.

  7. Repeat from step 3, if necessary.

The following code fragment acquires to on-board memory from CH A in single channel mode:

// 1. Set record size
AlazarSetRecordSize (
  boardHandle, // HANDLE -- board handle
  preTriggerSamples, // U32 -- pre-trigger samples
  postTriggerSamples // U32 -- post-trigger samples
  );

// 2. Set record count
AlazarSetRecordCount(
  boardHandle, // HANDLE -- board handle
  recordsPerCapture // U32 -- records per acquisition
  );

// 3. Enable single channel mode
AlazarSetParameter(
  boardHandle, // HANDLE -- board handle
  0, // U8 -- channel Id (not used)
  SET_SINGLE_CHANNEL_MODE, // U32 -- parameter
  CHANNEL_A // long – CHANNEL_A or CHANNEL_B
  );

// 4. Arm the board to wait for trigger events
AlazarStartCapture(boardHandle);

// 5. Wait for the board to receive all trigger events
//    and capture all records to on-board memory
while (AlazarBusy (boardHandle))
{
  // The acquisition is in progress
}

// 6. The acquisition is complete. Call AlazarRead or
//    AlazarHyperDisp to transfer records from on-board memory
//    to your buffer.

Note

A call to AlazarSetParameter() must be made before each call to AlazarStartCapture().

If the of number of samples per record specified in AlazarSetRecordSize() is greater than the maximum number of samples per channel in dual-channel mode, but is less than the maximum number of samples per record in single-channel mode, and AlazarSetParameter() is not called before calling AlazarStartCapture(), then AlazarStartCapture() will fail with error ApiNotSupportedInDualChannelMode.

Using AlazarRead#

Use AlazarRead() to transfer samples from records acquired to on-board memory to a buffer in host memory.

Transferring full records#

The following code fragment transfers a full CH A record from on-board memory to a buffer in host memory:

// Allocate a buffer to hold one record.
// Note that the buffer must be at least 16 samples
// larger than the number of samples per record.
U32 allocBytes = bytesPerSample * (samplesPerRecord + 16);
void* buffer = malloc(allocBytes);

// Transfer a CHA record into our buffer
AlazarRead (
  boardHandle, // HANDLE -- board handle
  CHANNEL_A, // U32 -- channel Id
  buffer, // void* -- buffer
  bytesPerSample, // int -- bytes per sample
  (long) record, // long -- record (1 indexed)
  -((long)preTriggerSamples), // long -- trigger offset
  samplesPerRecord // U32 -- samples to transfer
  );

See “%ATS_SDK_DIR%\Samples\SinglePort\AR” for a complete sample program that demonstrates how to use AlazarRead() to read full records.

Transferring partial records#

AlazarRead() can transfer a segment of a record from on-board memory to a buffer in host memory. This may be useful if:

  • The number of bytes in a full record in on-board memory exceeds the buffer size in bytes that an application can allocate in host memory.

  • An application wishes to reduce the time required for data transfer when it acquires relatively long records to on-board memory, but is only interested in a relatively small part of the record.

Use the transferOffset parameter in the call to AlazarRead() to specify the offset, in samples from the trigger position in the record, of the first sample to transfer from on-board memory to the application buffer. And use the transferLength parameter to specify the number of samples to transfer from on-board memory to the application buffer, where this number of samples may be less than the number of samples per record. The following code fragment divides a record into segments, and transfers the segments from on-board to host memory:

// Allocate a buffer to hold one record segment.
// Note that the buffer must be at least 16 samples
// larger than the number of samples per buffer.
U32 allocBytes = bytesPerSample * (samplesPerBuffer + 16);
void* buffer = malloc(allocBytes);

// Transfer a record in segments from on-board memory
U32 samplesToRead = samplesPerRecord;
long triggerOffset_samples = -(long)preTriggerSamples;
while (samplesToRead > 0) {
  // Transfer a record segment from on-board memory
  U32 samplesThisRead;
  if (samplesToRead > samplesPerBuffer)
    samplesThisRead = samplesPerBuffer;
  else
    samplesThisRead = samplesToRead;
  AlazarRead (
    boardHandle, // HANDLE -- board handle
    CHANNEL_A, // U32 -- channel Id
    buffer, // void* -- buffer
    bytesPerSample, // int -- bytes per sample
    (long) record, // long -- record (1 indexed)
    triggerOffset_samples, // long -- trigger offset
    samplesThisRead // U32 -- samples to transfer
    );

  // Process the record segment here
  WriteSamplesToFile(buffer, samplesThisRead);

  // Point to next record segment in on-board memory
  triggerOffset_samples += samplesThisRead;

  // Decrement number of samples left to read
  samplesToRead -= samplesThisRead;
}

See “%ATS_SDK_DIR%\Samples\SinglePort\AR_Segments” for a complete sample program that demonstrates how to read records in segments.

Using AlazarReadEx#

AlazarRead() can transfer samples from records acquired to on-board memory that contain up to 2,147,483,647 samples. If a record contains 2,147,483,648 or more samples, use AlazarReadEx() rather than AlazarRead(). AlazarReadEx() uses signed 64-bit transfer offsets, while AlazarRead() uses signed 32-bit transfer offsets. Otherwise, AlazarReadEx() and AlazarRead() are identical.

Using AlazarHyperDisp#

HyperDisp technology enables the FPGA on an AlazarTech digitizer board to process sample data. The FPGA divides a record in on-board memory into intervals, finds the minimum and maximum sample values during each interval, and transfers an array of minimum and maximum value pairs to host memory. This allows the acquisition of relatively long records to on-board memory, but the transfer of relatively short processed records across the PCI/PCIe bus to host memory.

For example, an ATS860-256M would require over 2 seconds per channel to transfer 256,000,000 samples across the PCI bus. However, with HyperDisp enabled the ATS860 would require a fraction of a second to calculate HyperDisp data, and transfer a few kilobytes of processed data across the PCI bus. If an application was searching these records for glitches, it may save a considerable amount of time by searching HyperDisp data for the glitches and, if a glitch were found, transfer the raw sample data from the interval from on-board memory to host memory.

Use AlazarHyperDisp() to enable a board to process records in on-board memory, and transfer processed records to host memory. The following code fragment enables an ATS860-256M to process a record in on-board memory containing 250,000,000 samples into an array of 100 HyperDisp points, where each point contains the minimum and maximum sample values over an interval of 2,500,000 samples in the record:

// Specify number of samples per record
U32 preTriggerSamples = 125000000;
U32 postTriggerSamples = 125000000;
U32 samplesPerRecord = preTriggerSamples + postTriggerSamples;
U32 recordsPerCapture = 1;

// Acquire to on-board memory (omitted)
// Specify the number of HyperDisp points
U32 pointsPerRecord = 100;

// Allocate a buffer to store the HyperDisp data
U32 bytesPerSample = 1; // ATS860 constant
U32 samplesPerPoint = 2; // HyperDisp constant
U32 bytesPerBuffer = bytesPerSample * samplesPerPoint * pointsPerRecord;
U8 *buffer = (U8*) malloc(bytesPerBuffer);

// Enable ATS860 FPGA to process the 250M sample record
// in on-board memory into an array of 100 HyperDisp points,
// and transfer the HyperDisp points into our buffer
U32 error;

AlazarHyperDisp (
  boardHandle, // HANDLE -- board handle
  NULL, // void* -- reserved
  samplesPerRecord, // U32 -- BufferSize
  (U8*) buffer, // U8* -- ViewBuffer
  bytesPerBuffer, // U32 -- ViewBufferSize
  pointsPerRecord, // U32 -- NumOfPixels
  1, // U32 -- Option (1 = HyperDisp)
  CHANNEL_A, // U32 -- ChannelSelect
  1, // U32 -- record (1 indexed)
  -(long)preTriggerSamples, // long -- TransferOffset
  &error // U32* -- error
  );

See “%ATS_SDK_DIR%\Samples\SinglePort\HD” for a complete sample program that demonstrates how to use AlazarHyperDisp().

Record timestamps#

AlazarTech digitizer boards include a 40-bit counter clocked by the sample clock source scaled by a board specific divider. When a board receives a trigger event to capture a record to on-board memory, it latches and saves the value of this counter. The counter value gives the time, relative to when the counter was reset, when the trigger event for the record occurred.

By default, this counter is reset to zero at the start of each acquisition. Use AlazarResetTimeStamp() to control when the record timestamp counter is reset.

Use AlazarGetTriggerAddress() to retrieve the timestamp, in timestamp clock ticks, of a record acquired to on-board memory. This function does not convert the timestamp value to seconds. The following code fragment gets the record timestamp of a record acquired to on-board memory, and converts the timestamp value from clocks ticks to seconds:

// Read the record timestamp
U32 triggerAddress;
U32 timestampHigh;
U32 timestampLow;

AlazarGetTriggerAddress (
boardHandle, // HANDLE -- board handle
record, // U32 -- record number (1-indexed)
&triggerAddress, // U32* -- trigger address
&timestampHigh, // U32* -- timestamp high part
&timestampLow // U32* -- timestamp low part
);

// Convert the record timestamp from counts to seconds
__int64 timeStamp_cnt;
timeStamp_cnt = ((__int64) timestampHigh) << 8;
timeStamp_cnt |= timestampLow & 0x0ff;
double samplesPerTimestampCount = 2; // board specific constant
double samplesPerSec = 50.e6; // sample rate
double timeStamp_sec = (double) samplesPerTimestampCount *
                       timeStamp_cnt / samplesPerSec;

Call AlazarGetParameter() with the GET_SAMPLES_PER_TIMESTAMP_CLOCK parameter to obtain the board specific “samples per timestamp count” value. See Samples per record requirements for a list of these values. See “%ATS_SDK_DIR%\Samples\SinglePort\AR_Timestamps” for a complete sample program that demonstrates how to retrieve record timestamps and convert them to seconds.

Master-slave applications#

If the single-port API is used to acquire from master-slave board system, only the master board in the board system should receive calls to the following API functions: AlazarStartCapture(), AlazarAbortCapture(), AlazarBusy(), AlazarTriggered() and AlazarForceTrigger(). See “%ATS_SDK_DIR%\Samples\SinglePort\AR_MasterSlave” for a sample program that demonstrates how to acquire from a master-slave system.

Processing data#

Converting sample values to volts#

The data acquisition APIs transfer an array of sample values into an application buffer. Each sample value occupies 1 or 2 bytes in the buffer, where a sample code is stored in the most significant bits of the sample values. Sample values that occupy two bytes are stored with their least significant bytes at the lower byte addresses (little-endian byte order) in the buffer. To convert sample values in the buffer to volts:

  • Get a sample value from the buffer.

  • Get the sample code from the most-significant bits of the sample value.

  • Convert the sample code to volts.

Note that the arrangement of samples values in the buffer into records and channels depends on the API used to acquire the data.

  • Single-port acquisitions return a contiguous array of samples for a specified channel. (See Single Port Acquisition above.)

  • Dual-port AutoDMA acquisitions return sample data whose arrangement depends on the AutoDMA mode and options chosen. (See section Dual port AutoDMA Acquisition above.)

Also note that AlazarTech digitizer boards generate unsigned sample codes by default. (See Data format above.)

8-bits per sample#

Getting 1-byte sample values from the buffer#

The hexadecimal editor view below shows the first 128-bytes of data in a buffer from an 8-bit digitizer such as the ATS850, ATS860, ATS9850, and ATS9870.

00000  7F 7F 7F 7F 7F 7F 7F 7F   7F 7F 7F 7F 7F 7F 7F 7F
00010  7F 7F 7F 7F 7F 7F 7F 7F   7F 7F 7F 7F 7F 7F 7F 7F
00020  7F 7F 7F 7F 7F 7F 7F 7F   7F 7F 7F 7F 7F 7F 7F 7F
00030  7F 7F 7F 7F 7F 7F 7F 7F   7F 7F 7F 7F 7F 7F 7F 7F
00040  7F 7F 7F 7F 7F 7F 7F 7F   7F 7F 7F 7F 7F 7F 7F 7F
00050  7F 7F 7F 7F 7F 7F 7F 7F   7F 7F 7F 7F 7F 7F 7F 7F
00060  7F 7F 7F 7F 7F 7F 7F 7F   7F 7F 7F 7F 7F 7F 7F 7F
00070  7F 7F 7F 7F 7F 7F 7F 7F   7F 7F 7F 7F 7F 7F 7F 7F

Each 8-bit sample occupies 1-byte in the buffer, so the block above displays 128 samples (128 bytes / 1 byte per sample). The following code fragment demonstrates how to access each 8-bit sample value in a buffer:

U8 *pSamples = (U8*) buffer;
for (U32 sample = 0; sample < samplesPerBuffer; sample++) {
  U8 sampleValue = *pSamples++;
  printf("sample value = %02Xn", sampleValue);
}
Getting 8-bit sample codes from 1-byte sample values#

Each 8-bit sample value stores an 8-bit sample code. For example, the first byte in buffer above stores the sample code 0x7F, or 127 decimal.

Converting unsigned 8-bit sample codes to volts#

A sample code of 128 (0x80) represents ~0V input voltage, 255 (0xFF) represents a positive full-scale input voltage, and 0 represents a negative full-scale input voltage. The following table illustrates how unsigned 8-bit sample codes map to values in volts according to the full-scale input range of the input channel.

Hex value

Fraction of input range

Volts for ±100 mV range

Volts for ±1 V range

0x00

-100%

-100 mV

-1 V

0x40

-50%

-50 mV

-.5 V

0x80

0%

0 V

0V

0xC0

+50%

50 mV

+.5 V

0xFF

+100%

+100 mV

+1 V

The following code fragment shows how to convert a 1-byte sample value containing an unsigned 8-bit code to in volts:

double SampleToVoltsU8(U8 sampleValue, double inputRange_volts)
{
  // AlazarTech digitizers are calibrated as follows

  double codeZero = (double)UCHAR_MAX/2;
  double codeRange = (double)UCHAR_MAX/2;
  // Convert sample code to volts
  double sampleVolts = inputRange_volts *
  ((double) (sampleValue - codeZero) / codeRange);
  return sampleVolts;
}
Converting signed 8-bit sample codes to volts#

A signed code of 0 represents ~0V input voltage, 127 (0x7F) represents a positive full-scale input voltage, and –128 (0x80) represents a negative full-scale input voltage. The following table illustrates how signed 8-bit sample codes map to values in volts according to the full-scale input range of the input channel.

Hex value

Fraction of input range

Volts for ±100 mV range

Volts for ±1 V range

0x81

-100%

-100 mV

-1 V

0xC0

-50%

-50 mV

-.5 V

0x00

0%

0 V

0V

0x40

+50%

50 mV

+.5 V

0x7F

+100%

+100 mV

+1 V

The following code fragment shows how to convert a 1-byte sample value containing a signed 8-bit sample code to in volts:

double SampleToVoltsS8(S8 sampleValue, double inputRange_volts)
{
  // AlazarTech digitizers are calibrated as follows
  double codeZero = 0;
  double codeRange = (double)SCHAR_MAX;
  // Convert sample code to volts
  double sampleVolts = inputRange_volts *
  ((double) (sampleCode - codeZero) / codeRange);
  return sampleVolts;
}

12-bits per sample#

Getting 2-byte sample values from the buffer#

The hexadecimal editor view below displays the first 128-bytes of data in a buffer from a 12-bit digitizer such as the ATS310, ATS330, ATS9325, ATS9350, ATS9351, ATS9352, ATS9353, ATS9360, ATS9371, and ATS9373.

00000  E0 7F F0 7F 00 80 F0 7F   F0 7F 10 80 E0 7F 00 80
00010  F0 7F 00 80 E0 7F E0 7F   00 80 E0 7F F0 7F F0 7F
00020  E0 7F F0 7F 00 80 F0 7F   F0 7F 10 80 E0 7F 00 80
00030  F0 7F 00 80 E0 7F E0 7F   00 80 E0 7F F0 7F F0 7F
00040  E0 7F F0 7F 00 80 F0 7F   F0 7F 10 80 E0 7F 00 80
00050  F0 7F 00 80 E0 7F E0 7F   00 80 E0 7F F0 7F F0 7F
00060  E0 7F F0 7F 00 80 F0 7F   F0 7F 10 80 E0 7F 00 80
00070  F0 7F 00 80 E0 7F E0 7F   00 80 E0 7F F0 7F F0 7F

Each 12-bit sample value occupies a 2-bytes in the buffer, so the view above displays 64 sample values (128 bytes / 2 bytes per sample). The first 2 bytes in the buffer are 0xE0 and 0x7F. Two-byte sample values are stored in little-endian byte order in the buffer, so the first sample value in the buffer is 0x7FE0. The following code fragment demonstrates how to access each 16-bit sample value in a buffer:

U16 *pSamples = (U16*)buffer;
for (U32 sample = 0; sample < samplesPerBuffer; sample++) {
  U16 sampleValue = *pSamples++;
  printf("sample value = %04X\n", sampleValue);
}
Getting 12-bit sample codes from 16-bit sample values#

A 12-bit sample code is stored in the most significant bits (MSB) of each 16-bit sample value, so right-shift each 16-bit value by 4 (or divide by 16) to obtain the 12-bit sample code. In the example above, the 16-bit sample value 0x7FE0 right-shifted by four results in the 12-bit sample code 0x7FE, or 2046 decimal.

16-bit sample value in decimal

32736

16-bit sample value in hex

7FE0

16-bit sample value in binary

0111 1111 1110 0000

12-bit sample code from MSBs of 16-bit value

0111 1101 1110

12-bit sample code in hex

7FE

12-bit sample code in decimal

2046

Converting unsigned 12-bit sample codes to volts#

An unsigned code of 2048 (0x800) represents ~0V input voltage, 4095 (0xFFF) represents a positive full-scale input voltage, and 0 represents a negative full-scale input voltage. The following table illustrates how unsigned 12-bit sample codes map to values in volts according to the full-scale input range of the input channel.

Hex value

Fraction of input range

Volts for ±100 mV range

Volts for ±1 V range

0x000

-100%

-100 mV

-1 V

0x400

-50%

-50 mV

-.5 V

0x800

0%

0 V

0V

0xC00

+50%

50 mV

+.5 V

0xFFF

+100%

+100 mV

+1 V

The following code fragment demonstrates how to convert a 2-byte word containing an unsigned 12-bit sample code to in volts:

double SampleToVoltsU12(U16 sampleValue, double inputRange_volts)
{
  // Right-shift 16-bit sample word by 4 to get 12-bit sample code
  int bitShift = 4;
  U16 sampleCode = sampeValue >> bitShift;
  // AlazarTech digitizers are calibrated as follows
  int bitsPerSample = 12;
  double codeZero = (1 << (bitsPerSample - 1)) - 0.5;
  double codeRange = (1 << (bitsPerSample - 1)) - 0.5;
  // Convert sample code to volts
  double sampleVolts = inputRange_volts *
  ((double) (sampleCode - codeZero) / codeRange);
  return sampleVolts;
}
Converting signed 12-bit sample codes to volts#

A signed code of 0 represents ~0V input voltage, 2047 (0x7FF) represents a positive full-scale input voltage, and -2048 (0x801) represents a negative full-scale input voltage. The following table illustrates how signed 12-bit sample codes map to values in volts according to the full-scale input range of the input channel.

Hex value

Fraction of input range

Volts for ±100 mV range

Volts for ±1 V range

0x801

-100%

-100 mV

-1 V

0xC00

-50%

-50 mV

-.5 V

0x000

0%

0 V

0V

0x400

+50%

50 mV

+.5 V

0x7FF

+100%

+100 mV

+1 V

The following code fragment shows how to convert a 2-byte sample word containing a signed 12-bit sample code to in volts:

double SampleToVoltsS12(S16 sampleValue, double inputRange_volts)
{
  // Right-shift 16-bit sample value by 4 to get 12-bit sample code
  int bitShift = 4;
  U16 sampleCode = sampleValue >> bitShift;
  // AlazarTech digitizers are calibrated as follows
  int bitsPerSample = 12;
  double codeZero = 0;
  double codeRange = (1 << (bitsPerSample - 1)) - 1;
  // Convert sample code to volts
  double sampleVolts = inputRange_volts *
  ((double) (sampleCode - codeZero) / codeRange);
  return sampleVolts;
}

14-bits per sample#

Getting 2-byte sample values from the buffer#

The hexadecimal editor view below displays the first 128-bytes of data in a buffer from a 14-bit digitizer such as the ATS460 and ATS9440.

00000  4C 7F EC 7f 3c 80 98 80   D0 80 24 81 7C 81 B4 81
00010  3C 82 B4 82 A8 82 60 83   9C 83 14 84 40 84 88 84
00020  E0 84 50 85 D0 85 FC 85   2C 86 B0 86 10 87 56 87
00030  4C 7F EC 7f 3c 80 98 80   D0 80 24 81 7C 81 B4 81
00040  3C 82 B4 82 A8 82 60 83   9C 83 14 84 40 84 88 84
00050  E0 84 50 85 D0 85 FC 85   2C 86 B0 86 10 87 56 87
00060  4C 7F EC 7f 3c 80 98 80   D0 80 24 81 7C 81 B4 81
00070  E0 84 50 85 D0 85 FC 85   2C 86 B0 86 10 87 56 87

Each sample value occupies a 2-bytes in the buffer, so the figure displays 64 sample values (128 bytes / 2 bytes per sample). The first 2 bytes in the buffer, shown highlighted, are 0x4C and 0x7F. Two-byte sample values are stored in little-endian byte order in the buffer, so the first sample value in the buffer is 0x7F4C. The following code fragment demonstrates how to access each 16-bit sample value in a buffer:

U16 *pSamples = (U16*) buffer;
for (U32 sample = 0; sample < samplesPerBuffer; sample++) {
  U16 sampleValue = *pSamples++;
  printf("sample value = %04X\n", sampleValue);
}
Getting 14-bit sample codes from 16-bit sample values#

A 14-bit sample code is stored in the most significant bits of each 16-bit sample value in the buffer, so right-shift each 16-bit value by 2 (or divide by 4) to obtain the 14-bit sample code. In the example above, the 16-bit value 0x7F4C right-shifted by two results in the 14-bit sample code 0x1FD3, or 8147 decimal.

16-bit sample value in decimal

32588

16-bit sample value in hex

7F4C

16-bit sample value in binary

0111 1111 0100 1100

14-bit sample code from MSBs of 16-bit sample value

01 1111 1101 0011

14-bit sample code in hex

1FD3

14-bit sample code in decimal

8147

Converting unsigned 14-bit sample codes to volts#

An unsigned code of 8192 (0x2000) represents ~0V input voltage, 16383 (0x3FFF) represents a positive full-scale input voltage, and 0 represents a negative full-scale input voltage. The following table illustrates how unsigned 14-bit sample codes map to values in volts according to the full-scale input range of an input channel.

Hex value

Fraction of input range

Volts for ±100 mV range

Volts for ±1 V range

0x0000

-100%

-100 mV

-1 V

0x1000

-50%

-50 mV

-.5 V

0x2000

0%

0 V

0V

0x3000

+50%

50 mV

+.5 V

0x3FFF

+100%

+100 mV

+1 V

The following code fragment demonstrates how to convert a 2-byte sample value containing an unsigned 14-bit sample code to in volts:

double SampleToVoltsU14(U16 sampleValue, double inputRange_volts)
{
  // Right-shift 16-bit sample word by 2 to get 14-bit sample code
  int bitShift = 2;
  U16 sampleCode = sampleValue >> bitShift;
  // AlazarTech digitizers are calibrated as follows
  int bitsPerSample = 14;
  double codeZero = (1 << (bitsPerSample - 1)) - 0.5;
  double codeRange = (1 << (bitsPerSample - 1)) - 0.5;
  // Convert sample code to volts
  double sampleVolts = inputRange_volts *
  ((double) (sampleCode - codeZero) / codeRange);
  return sampleVolts;
}
Converting signed 14-bit sample codes to volts#

A signed code of 0 represents ~0V input voltage, 8191 (0x1FFF) represents a positive full-scale input voltage, and –8192 (0x2000) represents a negative full-scale input voltage. The following table illustrates how signed 14-bit sample codes map to values in volts depending on the full-scale input range of the input channel.

Hex value

Fraction of input range

Volts for ±100 mV range

Volts for ±1 V range

0x2001

-100%

-100 mV

-1 V

0x3000

-50%

-50 mV

-.5 V

0x0000

0%

0 V

0V

0x1000

+50%

50 mV

+.5 V

0x1FFF

+100%

+100 mV

+1 V

The following code fragment demonstrates how to convert a 2-byte sample value containing a signed 14-bit sample code to in volts:

double SampleToVoltsS14(S16 sampleValue, double inputRange_volts)
{
  // Right-shift 16-bit sample word by 2 to get 14-bit sample code
  int bitShift = 2;
  U16 sampleCode = sampleValue >> bitShift;
  // AlazarTech digitizers are calibrated as follows
  int bitsPerSample = 14;
  double codeZero = 0;
  double codeRange = (1 << (bitsPerSample - 1)) - 1;
  // Convert sample code to volts
  double sampleVolts = inputRange_volts *
  ((double) (sampleCode - codeZero) / codeRange);
  return sampleVolts;
}

16-bit per sample#

Getting 2-byte sample values from the buffer#

The hexadecimal editor view below displays the first 128-bytes of data in a buffer from a 16-bit digitizer such as the ATS660, ATS9462, ATS9625, or ATS9626.

00000  14 80 FB 7F FB 7F 08 80   FB 7F 00 80 02 80 ED 7F
00010  0B 80 FF 7F F8 7F 0B 80   09 80 0E 80 F3 7F FE 7F
00020  14 80 FB 7F FB 7F 08 80   FB 7F 00 80 02 80 ED 7F
00030  0B 80 FF 7F F8 7F 0B 80   09 80 0E 80 F3 7F FE 7F
00040  14 80 FB 7F FB 7F 08 80   FB 7F 00 80 02 80 ED 7F
00050  0B 80 FF 7F F8 7F 0B 80   09 80 0E 80 F3 7F FE 7F
00060  14 80 FB 7F FB 7F 08 80   FB 7F 00 80 02 80 ED 7F
00070  14 80 FB 7F FB 7F 08 80   FB 7F 00 80 02 80 ED 7F

Each 16-bit sample value occupies 2 bytes in the buffer, so the figure displays 64 sample values (128 bytes / 2 bytes per sample). The first 2 bytes in the buffer are 0x14 and 0x80. Two-byte samples values are stored in little-endian byte order in the buffer, so the first sample value is 0x8014. The following code fragment demonstrates how to access each 16-bit sample value in a buffer:

U16 *pSamples = (U16*)buffer;
for (U32 sample = 0; sample < samplesPerBuffer; sample++)
{
  U16 sampleValue = * pSamples++;
  printf("sample value = %04X\n", sampleValue);
}
Getting 16-bit sample codes from 16-bit sample values#

A 16-bit sample code is stored in each 16-bit sample value in the buffer. In the example above, the first sample code is 0x8014, or 32788 decimal.

Converting unsigned 16-bit sample codes to volts#

An unsigned code of 32768 (0x8000) represents ~0V input voltage, 65535 (0xFFFF) represents a positive full-scale input voltage, and 0 represents a negative full-scale input voltage. The following table illustrates how unsigned 16-bit sample codes map to values in volts according to the full-scale input range of an input channel.

Hex value

Fraction of input range

Volts for ±100 mV range

Volts for ±1 V range

0x0000

-100%

-100 mV

-1 V

0x4000

-50%

-50 mV

-.5 V

0x8000

0%

0 V

0V

0xC000

+50%

50 mV

+.5 V

0xFFFF

+100%

+100 mV

+1 V

The following code fragment demonstrates how to convert a 2-byte sample value containing an unsigned 16-bit sample code to in volts:

double SampleToVoltsU16(U16 sampleValue, double inputRange_volts)
{
  // AlazarTech digitizers are calibrated as follows
  double codeZero = (double) USHRT_MAX/2;
  double codeRange = (double) USHRT_MAX/2;
  // Convert sample code to volts
  double sampleVolts = inputRange_volts *
  ((double) (sampleValue - codeZero) / codeRange);
  return sampleVolts;
}
Converting signed 16-bit sample codes to volts#

A signed code of 32767 (0x7FFF) represents a positive full-scale input voltage, 0 represents ~0V input voltage, and –32768 (0x8000) represents a negative full-scale input voltage. The following table illustrates how signed 16-bit sample codes map to values in volts according to the full-scale input range of the input channel:

Hex value

Fraction of input range

Volts for ±100 mV range

Volts for ±1 V range

0x8001

-100%

-100 mV

-1 V

0xC000

-50%

-50 mV

-.5 V

0x0000

0%

0 V

0V

0x4000

+50%

50 mV

+.5 V

0x7FFF

+100%

+100 mV

+1 V

The following code fragment demonstrates how to convert a 2-byte sample word containing a signed 16-bit sample code to in volts:

double SampleToVoltsS16(S16 sampleValue, double inputRange_volts)
{
  // AlazarTech digitizers are calibrated as follows
  double codeZero = 0;
  double codeRange = SHRT_MAX;
  // Convert sample code to volts
  double sampleVolts = inputRange_volts *
  ((double) (sampleCode - codeZero) / codeRange);
  return sampleVolts;
}

Saving binary files#

If an application saves sample data to a binary data file for later processing, it may be possible to improve disk write speeds by considering the following recommendations.

C/C++ applications#

If the application is written in C/C++ and is running under Windows, use the Windows CreateFile API with the FILE_FLAG_NO_BUFFERING flag for file I/O, if possible. Sequential disk write speeds are often substantially higher when this option is selected. See “%ATS_SDK_DIR%\Samples\DualPort\TS_DisableFileCache” for a sample program that demonstrates how to use this API to stream data to disk.

LabVIEW applications#

If the application is written in LabVIEW, or another high-level programming environment, then consider using the AlazarCreateStreamFile() API function. This function creates a binary data file, and enables the API to save each buffer received during an AutoDMA acquisition to this file. The API uses high-performance disk I/O functions that would be difficult to implement in high-level environments like LabVIEW. As a result, it allows an application in such an environment to perform high-performance disk streaming with a single additional function call. The following code fragment outlines how to write a disk streaming application using AlazarCreateStreamFile():

// Allow the API to allocate and manage AutoDMA buffers
flags |= ADMA_ALLOC_BUFFERS;

// Configure the board to make an AutoDMA acquisition
AlazarBeforeAsyncRead(
  handle, // HANDLE -- board handle
  channelMask, // U32 -- enabled channel mask
  -(long)preTriggerSamples, // long -- trigger offset
  samplesPerRecord, // U32 -- samples per record
  recordsPerBuffer, // U32 -- records per buffer
  recordsPerAcquisition, // U32 -- records per acquisition
  flags // U32 -- AutoDMA mode and options
  );

// Create a binary data file, and enable the API save each
// AutoDMA buffer to this file.
AlazarCreateStreamFile(handle, "data.bin");

// Arm the board to begin the acquisition
AlazarStartCapture(handle);

// Wait for each buffer in the acquisition to be filled
RETURN_CODE retCode = ApiSuccess;
while (retCode == ApiSuccess) {
  // Wait for the board to receive sufficient trigger
  // events to fill an internal buffer.
  // The API will save the buffer to a binary data file,
  // but will not copy any data into our buffer.
  retCode =
  AlazarWaitNextAsyncBufferComplete(
    handle, // HANDLE -- board handle
    NULL, // void* -- buffer to receive data
    0, // U32 -- bytes to copy into buffer
    timeout_ms // U32 -- time to wait for buffer
    );
}

// Abort the acquisition and release resources.
// This function must be called after an acquisition.
AlazarAbortAsyncRead(boardHandle);

See “%ATS_SDK_DIR%\Samples\DualPort\CS_CreateStreamFile” for a full sample program that demonstrates how to stream sample data to disk using AlazarCreateStreamFile().