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¶
In 10 MHz PLL external clock mode, the ATS9350, ATS9351 and ATS9352 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:
Call
AlazarSetTriggerOperation()
withTRIG_EXTERNAL
as the trigger source identifier of at least one of the trigger engines; andCall
AlazarSetExternalTrigger()
to select the range and coupling of the 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.
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…] |
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…] |
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 …] |
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 …] |
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 InputImpedence: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()
orTraditional()
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()
orTraditional()
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.
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:
Call
AlazarSetRecordSize()
to set the number of samples per record, where a record may contain samples before and after its trigger event.Call
AlazarSetRecordCount()
to set the number records per acquisition – the board captures one record per trigger event.Call
AlazarStartCapture()
to arm the board to wait for trigger events.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.Call
AlazarRead()
,AlazarReadEx()
, orAlazarHyperDisp()
to transfer records from on-board memory to host memory.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:
Call
AlazarSetRecordSize()
to set the number of samples per record, where a record may contain samples before and after its trigger event.Call
AlazarSetRecordCount()
to set the number records per acquisition – the board captures one record per trigger event.Call
AlazarSetParameter()
with the parameterSET_SINGLE_CHANNEL_MODE
, and specify the channel to use all memory.Call
AlazarStartCapture()
to arm the board to wait for trigger events.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.Call
AlazarRead()
,AlazarReadEx()
, orAlazarHyperDisp()
to transfer records from on-board memory to host memory.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
×tampHigh, // U32* -- timestamp high part
×tampLow // 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, 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()
.