==================== Programmer's Guide ==================== .. highlight:: c 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 :c:func:`AlazarNumOfSystems` function to determine the number of board systems detected by the SDK, and call the :c:func:`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 :c:func:`AlazarSetLED` function allows an application to control the LED on the PCI/PCIe mounting bracket of a board specified by its handle. Use the :c:func:`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: :c:func:`AlazarGetBoardKind` Get a board’s model from its handle. :c:func:`AlazarGetChannelInfo` Get the number of bits per sample, and on-board memory size in samples per channel. :c:func:`AlazarGetCPLDVersion` Get the CPLD version of a board. :c:func:`AlazarGetDriverVersion` Get the driver version of a board. :c:func:`AlazarGetParameter` Get a board parameter as a signed 32-bit value. :c:func:`AlazarGetParameterUL` Get a board parameter as an unsigned 32-bit value. :c:func:`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 :c:func:`AlazarSetCaptureClock` specifying :c:member:`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 :c:func:`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 :c:func:`AlazarSetCaptureClock` specifying :c:member:`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 :c:func:`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 :c:func:`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 :c:func:`AlazarSetCaptureClock` specifying :c:member:`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 :c:func:`AlazarSetCaptureClock` specifying :c:member:`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 :c:func:`AlazarSetCaptureClock` specifying :c:member:`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 :c:func:`AlazarSetCaptureClock` specifying :c:member:`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 :c:func:`AlazarSetCaptureClock` specifying :c:member:`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 :c:func:`AlazarSetCaptureClock` specifying :c:member:`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 :c:func:`AlazarSetCaptureClock` specifying :c:member:`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 :c:func:`AlazarSetCaptureClock` specifying :c:member:`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 :c:func:`AlazarSetCaptureClock` specifying :c:member:`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 :c:func:`AlazarSetCaptureClock` specifying :c:member:`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 :c:func:`AlazarSetCaptureClock` specifying :c:member:`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 :c:func:`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 :c:func:`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 :c:func:`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 :c:func:`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: :c:member:`TRIG_ENGINE_J` Configure trigger engine J :c:member:`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 :c:func:`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. :c:member:`TRIGGER_SLOPE_POSITIVE` The trigger engine detects a trigger event when sample values from the trigger source rise above a specified level. :c:member:`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: :c:member:`TRIG_ENGINE_OP_J` The board triggers when trigger engine J detects a trigger event. Events detected by engine K are ignored. :c:member:`TRIG_ENGINE_OP_K` The board triggers when trigger engine K detects a trigger event. Events detected by engine J are ignored. :c:member:`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 :c:func:`AlazarSetTriggerOperation` with :c:member:`TRIG_EXTERNAL` as the trigger source identifier of at least one of the trigger engines; and - Call :c:func:`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 :c:func:`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 :c:func:`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 :c:func:`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 :c:func:`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 :c:func:`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 :c:member:`PACK_MODE` parameter and a packing option (either :c:member:`PACK_DEFAULT`, :c:member:`PACK_8_BITS_PER_SAMPLE` or :c:member:`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 :c:func:`AlazarSetCaptureClock`:: AlazarSetParameterUL( handle, // HANDLE -- board handle channelMask, // U8 -- channel to acquire SET_ADC_MODE, ADC_MODE_DES ); Programs that wish to use DES-capable digitizers in non-DES mode (i.e. ATS9373 at sampling frequencies at or below 2GS/s) do not need to be modified. NPT footers ----------- Footers can be included to the data and contain additional information about the acquisition of each record. The footers include a timestamp, the record number in the current acquisition, a frame count and the state of the AUX I/O signal at the time of the acquisition. As the name implies, this option is only available in NPT acquisition mode. Depending if on-FPGA FFT is used or not, the function to retrieve the NPT footers and their position in memory is different. If FFT is not enabled, NPT footers will *replace* the last 16 bytes of a record, leading to a loss of a few data points. These NPT footers are labeled Time-Domain to highlighting the fact that FFT is not used. When one channel is enabled, the last 8 samples of the data will be removed. When two channels are enabled, only one footer will be appended per record and will take the place of the last 4 samples from each channel. When using on-FPGA FFT, a 128-byte word will be appended to each record. The last 16 bytes of this 128-byte word contain the footer. For convenience, a structure named :c:member:`NPTFooter` should be used. Here is how to enable and obtain the NPT footers: - Connect the start of frame signal to the AUX I/O connector. - Append the flag :c:member:`ADMA_ENABLE_RECORD_FOOTERS` to the options passed to :c:func:`AlazarBeforeAsyncRead` by using a binary OR (`|`). Make sure the acquisition mode is set to :c:member:`ADMA_NPT` and FFT processing is enabled if applicable. - Call :c:func:`AlazarConfigureAuxIO` specifying :c:member:`AUX_IN_AUXILIARY` as the mode with `0` as parameter. - Create an array that will contain the NPT footers. This array needs to be contiguous in memory and can thus be a standard C array or a `std::vector` with preallocated size. - Call :c:func:`AlazarExtractTimeDomainNPTFooters` or :c:func:`AlazarExtractFFTNPTFooters` to retrieve the NPT footers for each buffer and store them in the array. The `recordSize_bytes` parameter needs to take into account the number of active channels. - Browse the array to see the frame associated with each record and count the number of records in each frame if needed. See the API reference documentation for details about the specific parameters to use with each function. 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 :c:member:`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 :c:func:`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 :c:func:`AlazarGetParameter` with the GET_SAMPLES_PER_TIMESTAMP_CLOCK parameter to determine the board specific “samples per timestamp count” value. :ref:`trigger-delay-alignment` 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 :c:func:`AlazarPostAsyncBuffer` to make buffers available to be filled by the board, and :c:func:`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 :c:func:`AlazarPostAsyncBuffer` and :c:func:`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 :c:func:`AlazarBeforeAsyncRead` with the :c:member:`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 :c:func:`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 :c:member:`ADMA_ALLOC_BUFFERS` flag and :c:func:`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 :c:member:`ADMA_ALLOC_BUFFERS`. An application can get or set the number of DMA buffers allocated by the API by calling :c:func:`AlazarGetParameter` or :c:func:`AlazarSetParameter` with the parameter :c:member:`SETGET_ASYNC_BUFFCOUNT`. Note that applications may combine :c:member:`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 :c:member:`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 :c:func:`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 :c:func:`AlazarAsyncRead` API. The following code fragment outlines how use :c:func:`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 :c:func:`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 :c:func:`AlazarAbortAsyncRead` if, at the end of an acquisition, any of the buffers that it supplies to a board have not been completed. :c:func:`AlazarAbortAsyncRead` completes any pending buffers, and unlocks them from memory. .. warning:: If an application exits without calling :c:func:`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 :c:func:`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 :c:func:`NPT` or :c:func:`Traditional` mode AutoDMA acquisition where the number of “records per buffer” is equal to the number of scan lines per frame. - Call :c:func:`AlazarStartCapture` to being the acquisition. - Supply a TTL pulse to the AUX I/O connector (or call :c:func:`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 :c:func:`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 :c:func:`NPT` or :c:func:`Traditional` mode AutoDMA acquisition where the number of “records per buffer” is equal to the number of scan lines per frame. - Call :c:func:`AlazarStartCapture` to begin the acquisition. - Call :c:func:`AlazarForceTriggerEnable` to arm the board to capture one frame. The board will wait for sufficient trigger events to capture the number of records in an AutoDMA buffer, and then wait for the next trigger enable event. Note that if the number of records per acquisition is set to infinite, software arms the digitizer once to make an AutoDMA acquisition with an infinite number of frames. The hardware will continue acquiring frame data until the acquisition is aborted. See “%ATS_SDK_DIR%\\Samples\\DualPort\\NPT_Scan” for sample programs that demonstrate how to make a scanning application using hardware trigger enable signals. Other scanning applications (NPT Footers) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In some other applications, an acquisition is divided several frames, but the number of records per frame is not constant. This happens in imaging applications such as intravascular OCT. The rotation speed of the imaging probe is not constant and the number of records (A-lines) may vary from one frame to the other. For this situation, the AUX I/O connector should not be used as a trigger enable input as in conventional scanning application. Instead, it can be used a frame counter. The frame number can be appended to each data record so the used can recover the frame number for each record and then reconstruct each frame correctly. These are called footers and can only be used in NPT acquisition mode. See the NPT footers section for more details about using NPT footers. Master-slave applications ~~~~~~~~~~~~~~~~~~~~~~~~~ If a dual-port acquisition API is used to acquire from master-slave board system: - Call :c:func:`AlazarBeforeAsyncRead` on all slave boards before the master board. - Call :c:func:`AlazarStartCapture` only on the master board. - Call :c:func:`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 :ref:`trigger-delay-alignment` 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 :c:func:`AlazarSetRecordCount` and :c:func:`AlazarSetRecordSize` for more information. The address of application buffers passed to the following data transfer functions must meet the buffer alignment requirement in :ref:`trigger-delay-alignment`: :c:func:`AlazarRead`, :c:func:`AlazarReadEx`, :c:func:`AlazarAsyncRead`, :c:func:`AlazarPostAsyncBuffer`, and :c:func:`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 :c:func:`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 :c:func:`AlazarSetParameter` with parameter SET_DATA_FORMAT before the start of an acquisition to set the sample data format, and call :c:func:`AlazarGetParameter` with GET_DATA_FORMAT to get the current data format. The following code fragment demonstrates how to select signed sample data output:: AlazarSetParameter( handle, // HANDLE -- board handle 0, // U8 -- channel Id (not used) SET_DATA_FORMAT, // U32 -- parameter to set DATA_FORMAT_SIGNED // long -- value (0 = unsigned, 1 = signed) ); Single port acquisition ----------------------- The single-port acquisition API allows an application to capture records to on-board memory – one per trigger event – and transfer records from on-board to host memory. Data acquisition and data transfer are made serially, so trigger events may be missed if they occur during data transfers. .. note:: The single port acquisition mode is not recommended for new designs. It should only be used with PCI bus digitizers that are not capable of making dual-port acquisitions: ATS460 and ATS860 without dual-port memory upgrade, ATS310, ATS330, ATS850. Acquiring to on-board memory ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ All channels mode ^^^^^^^^^^^^^^^^^ By default, AlazarTech digitizer boards share on-board memory equally between both of a board’s input channels. A single-port acquisition in dual-channel mode captures samples from both input channels simultaneously to on-board memory and, after the acquisition is complete, allows samples from either input channel to be transferred from on-board memory to an application buffer. To program a board acquire to on-board memory in dual-channel mode: 1. Call :c:func:`AlazarSetRecordSize` to set the number of samples per record, where a record may contain samples before and after its trigger event. 2. Call :c:func:`AlazarSetRecordCount` to set the number records per acquisition – the board captures one record per trigger event. 3. Call :c:func:`AlazarStartCapture` to arm the board to wait for trigger events. 4. Call :c:func:`AlazarBusy` in a loop to poll until the board has received all trigger events in the acquisition, and has captured all records to on-board memory. 5. Call :c:func:`AlazarRead`, :c:func:`AlazarReadEx`, or :c:func:`AlazarHyperDisp` to transfer records from on-board memory to host memory. 6. Repeat from step 3, if necessary. The following code fragment acquires to on board memory with on-board memory shared between both input channels:: // 1. Set record size AlazarSetRecordSize ( boardHandle, // HANDLE -- board handle preTriggerSamples, // U32 -- pre-trigger samples postTriggerSamples // U32 -- post-trigger samples ); // 2. Set record count AlazarSetRecordCount( boardHandle, // HANDLE -- board handle recordsPerCapture // U32 -- records per acquisition ); // 3. Arm the board to wait for trigger events AlazarStartCapture(boardHandle); // 4. Wait for the board to receive all trigger events and capture all // records to on-board memory while (AlazarBusy (boardHandle)) { // The acquisition is in progress } // 5. The acquisition is complete. Call AlazarRead or AlazarHyperDisp to // transfer records from on-board memory to your buffer. Single channel mode ^^^^^^^^^^^^^^^^^^^ .. note:: The single port acquisition mode is not recommended for new designs. It should only be used with digitizers that are not capable of making dual-port acquisitions: ATS310, ATS330 and ATS850. ATS9325, ATS9350, ATS9351, ATS9440, ATS9625, ATS9626, ATS9850, and ATS9870 and digitizer boards can be configured to dedicate all on-board memory to one of a board’s input channels. A single-port acquisition in single-channel mode only captures samples from the specified channel to on-board memory and, after the acquisition is complete, only allows samples from the specified channel to be transferred from on-board memory to an application buffer. To program a board to acquire to on-board memory in single-channel mode: 1. Call :c:func:`AlazarSetRecordSize` to set the number of samples per record, where a record may contain samples before and after its trigger event. 2. Call :c:func:`AlazarSetRecordCount` to set the number records per acquisition – the board captures one record per trigger event. 3. Call :c:func:`AlazarSetParameter` with the parameter :c:member:`SET_SINGLE_CHANNEL_MODE`, and specify the channel to use all memory. 4. Call :c:func:`AlazarStartCapture` to arm the board to wait for trigger events. 5. Call :c:func:`AlazarBusy` in a loop to poll until the board has received all trigger events in the acquisition, and has captured all records to on-board memory. 6. Call :c:func:`AlazarRead`, :c:func:`AlazarReadEx`, or :c:func:`AlazarHyperDisp` to transfer records from on-board memory to host memory. 7. Repeat from step 3, if necessary. The following code fragment acquires to on-board memory from CH A in single channel mode:: // 1. Set record size AlazarSetRecordSize ( boardHandle, // HANDLE -- board handle preTriggerSamples, // U32 -- pre-trigger samples postTriggerSamples // U32 -- post-trigger samples ); // 2. Set record count AlazarSetRecordCount( boardHandle, // HANDLE -- board handle recordsPerCapture // U32 -- records per acquisition ); // 3. Enable single channel mode AlazarSetParameter( boardHandle, // HANDLE -- board handle 0, // U8 -- channel Id (not used) SET_SINGLE_CHANNEL_MODE, // U32 -- parameter CHANNEL_A // long – CHANNEL_A or CHANNEL_B ); // 4. Arm the board to wait for trigger events AlazarStartCapture(boardHandle); // 5. Wait for the board to receive all trigger events // and capture all records to on-board memory while (AlazarBusy (boardHandle)) { // The acquisition is in progress } // 6. The acquisition is complete. Call AlazarRead or // AlazarHyperDisp to transfer records from on-board memory // to your buffer. .. note:: A call to :c:func:`AlazarSetParameter` must be made before each call to :c:func:`AlazarStartCapture`. If the of number of samples per record specified in :c:func:`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 :c:func:`AlazarSetParameter` is not called before calling :c:func:`AlazarStartCapture`, then :c:func:`AlazarStartCapture` will fail with error :c:member:`ApiNotSupportedInDualChannelMode`. Using AlazarRead ~~~~~~~~~~~~~~~~ Use :c:func:`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 :c:func:`AlazarRead` to read full records. Transferring partial records ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :c:func:`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 :c:func:`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 ~~~~~~~~~~~~~~~~~~ :c:func:`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 :c:func:`AlazarReadEx` rather than :c:func:`AlazarRead`. :c:func:`AlazarReadEx` uses signed 64-bit transfer offsets, while :c:func:`AlazarRead` uses signed 32-bit transfer offsets. Otherwise, :c:func:`AlazarReadEx` and :c:func:`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 :c:func:`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 :c:func:`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 :c:func:`AlazarResetTimeStamp` to control when the record timestamp counter is reset. Use :c:func:`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 :c:func:`AlazarGetParameter` with the :c:member:`GET_SAMPLES_PER_TIMESTAMP_CLOCK` parameter to obtain the board specific “samples per timestamp count” value. See :ref:`trigger-delay-alignment` 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: :c:func:`AlazarStartCapture`, :c:func:`AlazarAbortCapture`, :c:func:`AlazarBusy`, :c:func:`AlazarTriggered` and :c:func:`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. .. code-block:: none 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. .. code-block:: none 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. .. code-block:: none 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 :c:func:`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 :c:func:`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 :c:func:`AlazarCreateStreamFile`.