From 3fb9ef4b59c22b9d3b72546d684fa23877e2bd32 Mon Sep 17 00:00:00 2001 From: elebel-emerson Date: Tue, 20 Oct 2026 11:03:31 -0500 Subject: [PATCH 1/2] Add TMU Examples --- docs/nidigital/examples.rst | 45 +++++ .../examples/tmu_arm_types/PinMap.pinmap | 23 +++ .../tmu_arm_types/nidigital_tmu_arm_types.py | 148 +++++++++++++++ .../examples/tmu_debounce/PinMap.pinmap | 23 +++ .../tmu_debounce/nidigital_tmu_debounce.py | 126 +++++++++++++ .../tmu_getting_started/PinMap.pinmap | 23 +++ .../nidigital_tmu_getting_started.py | 111 +++++++++++ .../tmu_measurement_types/PinMap.pinmap | 23 +++ .../nidigital_tmu_measurement_types.py | 171 +++++++++++++++++ .../tmu_multi-instrument/PinMap.pinmap | 30 +++ .../nidigital_tmu_multi-instrument.py | 177 ++++++++++++++++++ 11 files changed, 900 insertions(+) create mode 100644 src/nidigital/examples/tmu_arm_types/PinMap.pinmap create mode 100644 src/nidigital/examples/tmu_arm_types/nidigital_tmu_arm_types.py create mode 100644 src/nidigital/examples/tmu_debounce/PinMap.pinmap create mode 100644 src/nidigital/examples/tmu_debounce/nidigital_tmu_debounce.py create mode 100644 src/nidigital/examples/tmu_getting_started/PinMap.pinmap create mode 100644 src/nidigital/examples/tmu_getting_started/nidigital_tmu_getting_started.py create mode 100644 src/nidigital/examples/tmu_measurement_types/PinMap.pinmap create mode 100644 src/nidigital/examples/tmu_measurement_types/nidigital_tmu_measurement_types.py create mode 100644 src/nidigital/examples/tmu_multi-instrument/PinMap.pinmap create mode 100644 src/nidigital/examples/tmu_multi-instrument/nidigital_tmu_multi-instrument.py diff --git a/docs/nidigital/examples.rst b/docs/nidigital/examples.rst index b8c8cc689a..e276efa404 100644 --- a/docs/nidigital/examples.rst +++ b/docs/nidigital/examples.rst @@ -30,3 +30,48 @@ nidigital_ppmu_source_and_measure.py :encoding: utf8 :caption: `(nidigital_ppmu_source_and_measure.py) `_ +nidigital_tmu_arm_types.py +-------------------------- + +.. literalinclude:: ../../src/nidigital/examples/tmu_arm_types/nidigital_tmu_arm_types.py + :language: python + :linenos: + :encoding: utf8 + :caption: `(nidigital_tmu_arm_types.py) `_ + +nidigital_tmu_debounce.py +------------------------- + +.. literalinclude:: ../../src/nidigital/examples/tmu_debounce/nidigital_tmu_debounce.py + :language: python + :linenos: + :encoding: utf8 + :caption: `(nidigital_tmu_debounce.py) `_ + +nidigital_tmu_getting_started.py +-------------------------------- + +.. literalinclude:: ../../src/nidigital/examples/tmu_getting_started/nidigital_tmu_getting_started.py + :language: python + :linenos: + :encoding: utf8 + :caption: `(nidigital_tmu_getting_started.py) `_ + +nidigital_tmu_measurement_types.py +---------------------------------- + +.. literalinclude:: ../../src/nidigital/examples/tmu_measurement_types/nidigital_tmu_measurement_types.py + :language: python + :linenos: + :encoding: utf8 + :caption: `(nidigital_tmu_measurement_types.py) `_ + +nidigital_tmu_multi-instrument.py +--------------------------------- + +.. literalinclude:: ../../src/nidigital/examples/tmu_multi-instrument/nidigital_tmu_multi-instrument.py + :language: python + :linenos: + :encoding: utf8 + :caption: `(nidigital_tmu_multi-instrument.py) `_ + diff --git a/src/nidigital/examples/tmu_arm_types/PinMap.pinmap b/src/nidigital/examples/tmu_arm_types/PinMap.pinmap new file mode 100644 index 0000000000..aff6a135ed --- /dev/null +++ b/src/nidigital/examples/tmu_arm_types/PinMap.pinmap @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/nidigital/examples/tmu_arm_types/nidigital_tmu_arm_types.py b/src/nidigital/examples/tmu_arm_types/nidigital_tmu_arm_types.py new file mode 100644 index 0000000000..cda26482e7 --- /dev/null +++ b/src/nidigital/examples/tmu_arm_types/nidigital_tmu_arm_types.py @@ -0,0 +1,148 @@ +""" +Demonstrates the difference between Immediate and Edge arm types for TMU +measurements using the NI-Digital Pattern Driver API. This example shows: + - Immediate arm: the TMU begins looking for start/stop events as soon as + tmu_initiate is called. When start and stop events differ (e.g., duty + cycle), the detection order is not guaranteed on a free-running signal, + which can produce negative measurements + - Edge arm: the TMU waits for a specific arm event before looking for + start/stop events, guaranteeing deterministic event ordering and + consistent (positive) measurements + - The constraint that edge arm source/event/polarity must match either the + start or stop source configuration + +Both sections measure the same quantity (duty cycle high) on the same channel +so the behavioral difference between the two arm types is clearly visible. + +Note: The DUT must be generating signals on the configured channel for TMU +measurements to complete. In simulate mode, the driver returns simulated +measurement values. +""" + +from pathlib import Path + +import nidigital + + +RESOURCE_NAME = "PXI1Slot5" +OPTIONS = {"Simulate": True, "DriverSetup": {"Model": "6571"}} +PIN_MAP_PATH = Path(__file__).with_name("PinMap.pinmap") + + +def main(): + # Initial Device Values + dut_pin1 = "DUTPin1" + + # Initialize the digital pattern instrument session. The reset input is + # TRUE to ensure the instrument starts in a known state. + with nidigital.Session( + RESOURCE_NAME, id_query=True, reset_device=True, options=OPTIONS + ) as session: + # Load the pin map for the instrument to allow referencing of pin names + # in the channel list of select driver functions. + session.load_pin_map(str(PIN_MAP_PATH)) + + # Configure voltage levels for all channels. The VOL and VOH levels + # define the thresholds used by the TMU for SOURCE_EVENT_VOL and + # SOURCE_EVENT_VOH events respectively. + session.configure_voltage_levels(0.0, 5.0, 0.5, 2.4, 0.0) + + # Select each TMU from its instrument context so nimi-python constructs + # the fully qualified names PXI1Slot5/tmu0 and PXI1Slot5/tmu1. + instrument = session.instruments[RESOURCE_NAME] + immediate_tmu = instrument.time_measurement_units[0] + edge_tmu = instrument.time_measurement_units[1] + + # =================================================================== + # Part 1: Duty Cycle High with Immediate Arm + # =================================================================== + # With Immediate arm, the TMU begins looking for start and stop events + # as soon as tmu_initiate is called. Because the start event (VOH Rising + # Edge) and stop event (VOH Falling Edge) are different, the first + # event detected after initiation could be either one. + # + # If the stop event (Falling Edge) happens to be detected first, the + # measurement result will be NEGATIVE, indicating the stop timestamp + # preceded the start timestamp. This is the fundamental ambiguity of + # Immediate arm with non-identical start/stop events on a free-running + # periodic signal. + print("=== Part 1: Duty Cycle High with IMMEDIATE Arm (TMU0) ===\n") + immediate_tmu.tmu_enabled = True + immediate_tmu.tmu_arm_type = nidigital.enums.TMUArmType.IMMEDIATE + + # Configure Immediate arm type on TMU0. + immediate_tmu.tmu_start_source = dut_pin1 + immediate_tmu.tmu_start_source_event = nidigital.enums.TMUSourceEvent.VOH + immediate_tmu.tmu_start_source_event_polarity = nidigital.enums.TMUPolarity.RISING + immediate_tmu.tmu_stop_source = dut_pin1 + immediate_tmu.tmu_stop_source_event = nidigital.enums.TMUSourceEvent.VOH + immediate_tmu.tmu_stop_source_event_polarity = nidigital.enums.TMUPolarity.FALLING + immediate_tmu.tmu_samples_to_acquire = 100 + immediate_tmu.tmu_sample_timeout = 10.0 + + # =================================================================== + # Part 2: Duty Cycle High with Edge Arm + # =================================================================== + # With Edge arm, the TMU waits for a specific arm event before it begins + # looking for start/stop events. This eliminates the ambiguity of + # Immediate arm by establishing a known starting point in the signal + # cycle. + # + # Here we configure the arm to match the start source (DUTPin1, VOH, + # Rising Edge). The sequence is: + # 1. TMU waits for arm event (Rising Edge at VOH on DUTPin1) + # 2. Arm event is captured as the start event + # 3. TMU then waits for the stop event (Falling Edge at VOH) + # 4. Measurement = stop timestamp - start timestamp (always positive) + # + # IMPORTANT CONSTRAINT: The edge arm source, event, and polarity must + # match either the start or stop source configuration. If they do not + # match, the driver returns an error. + print("\n=== Part 2: Duty Cycle High with EDGE Arm (TMU1) ===\n") + edge_tmu.tmu_enabled = True + edge_tmu.tmu_arm_type = nidigital.enums.TMUArmType.EDGE + + # Configure Edge arm type on TMU1. + edge_tmu.tmu_start_source = dut_pin1 + edge_tmu.tmu_start_source_event = nidigital.enums.TMUSourceEvent.VOH + edge_tmu.tmu_start_source_event_polarity = nidigital.enums.TMUPolarity.RISING + edge_tmu.tmu_stop_source = dut_pin1 + edge_tmu.tmu_stop_source_event = nidigital.enums.TMUSourceEvent.VOH + edge_tmu.tmu_stop_source_event_polarity = nidigital.enums.TMUPolarity.FALLING + edge_tmu.tmu_samples_to_acquire = 100 + edge_tmu.tmu_sample_timeout = 10.0 + + # Configure the edge arm to match the start source. This guarantees the + # rising edge (start) is always captured before the falling edge (stop), + # producing a consistent positive measurement. + edge_tmu.tmu_edge_arm_source = dut_pin1 + edge_tmu.tmu_edge_arm_source_event = nidigital.enums.TMUSourceEvent.VOH + edge_tmu.tmu_edge_arm_polarity = nidigital.enums.TMUPolarity.RISING + + # Initiate both TMUs back-to-back. By initiating both TMUs consecutively, + # they begin acquiring measurements on the same signal nearly + # simultaneously. + immediate_tmu.tmu_initiate() + edge_tmu.tmu_initiate() + immediate_measurement = immediate_tmu.tmu_fetch_averaged_measurement(10.0) + edge_measurement = edge_tmu.tmu_fetch_averaged_measurement(10.0) + + # The Immediate arm result may be positive or negative. A negative value + # means the falling edge (stop) was detected before the rising edge + # (start). This is expected with Immediate arm on a free-running signal + # when start and stop events differ. + print(f"Immediate Arm (TMU0) - Duty Cycle High: {immediate_measurement:.6e} seconds") + + # With Edge arm, the result is always positive because the arm event + # guarantees the start event is captured before the stop event. + print(f"Edge Arm (TMU1) - Duty Cycle High: {edge_measurement:.6e} seconds") + + immediate_tmu.tmu_abort() + edge_tmu.tmu_abort() + + # Disconnect all channels using programmable onboard switching. + session.pins[""].selected_function = nidigital.enums.SelectedFunction.DISCONNECT + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/src/nidigital/examples/tmu_debounce/PinMap.pinmap b/src/nidigital/examples/tmu_debounce/PinMap.pinmap new file mode 100644 index 0000000000..aff6a135ed --- /dev/null +++ b/src/nidigital/examples/tmu_debounce/PinMap.pinmap @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/nidigital/examples/tmu_debounce/nidigital_tmu_debounce.py b/src/nidigital/examples/tmu_debounce/nidigital_tmu_debounce.py new file mode 100644 index 0000000000..0a946709f0 --- /dev/null +++ b/src/nidigital/examples/tmu_debounce/nidigital_tmu_debounce.py @@ -0,0 +1,126 @@ +""" +Demonstrates how to use the NI-Digital Pattern Driver API to configure TMU +input debounce filtering. This example shows: + - Configuring debounce times for start and stop inputs + - The constraint that when the start source and stop source are the same + channel, the start and stop debounce times must be equal + - When start and stop sources are different channels, the debounce times + can be configured independently + +Debounce filtering helps reject noise and glitches on the TMU input signals by +requiring the signal to remain stable for the configured debounce time before a +threshold crossing is recognized as a valid event. + +Note: In simulate mode, the driver returns simulated measurement values. +""" + +from pathlib import Path + +import nidigital + + +RESOURCE_NAME = "PXI1Slot5" +OPTIONS = {"Simulate": True, "DriverSetup": {"Model": "6571"}} +PIN_MAP_PATH = Path(__file__).with_name("PinMap.pinmap") + + +def main(): + # Initial Device Values + dut_pin1 = "DUTPin1" + dut_pin2 = "DUTPin2" + + # Initialize the digital pattern instrument session. The reset input is + # TRUE to ensure the instrument starts in a known state. + with nidigital.Session( + RESOURCE_NAME, id_query=True, reset_device=True, options=OPTIONS + ) as session: + # Load the pin map for the instrument to allow referencing of pin names + # in the channel list of select driver functions. + session.load_pin_map(str(PIN_MAP_PATH)) + + # Configure voltage levels for all channels. The VOL and VOH levels + # define the thresholds used by the TMU for SOURCE_EVENT_VOL and + # SOURCE_EVENT_VOH events respectively. + session.configure_voltage_levels(0.0, 5.0, 0.5, 2.4, 0.0) + + instrument = session.instruments[RESOURCE_NAME] + tmu = instrument.time_measurement_units[0] + + # =================================================================== + # Example 1: Same Channel - Debounce Times Must Be Equal + # =================================================================== + # When the TMU start source and stop source are configured to the same + # channel, the start input debounce time and stop input debounce time + # MUST be set to the same value. If they differ, the driver returns an + # error. + # + # This constraint exists because the same physical input path is used + # for both start and stop events when they share a channel, so only one + # debounce filter setting can be applied. + print("--- Example 1: Same Channel Debounce (Equal Times) ---") + tmu.tmu_enabled = True + tmu.tmu_arm_type = nidigital.enums.TMUArmType.IMMEDIATE + + # Configure start and stop to the SAME channel (DUTPin1). This is a + # Period measurement configuration. + tmu.tmu_start_source = dut_pin1 + tmu.tmu_start_source_event = nidigital.enums.TMUSourceEvent.VOL + tmu.tmu_start_source_event_polarity = nidigital.enums.TMUPolarity.RISING + tmu.tmu_stop_source = dut_pin1 + tmu.tmu_stop_source_event = nidigital.enums.TMUSourceEvent.VOL + tmu.tmu_stop_source_event_polarity = nidigital.enums.TMUPolarity.RISING + tmu.tmu_samples_to_acquire = 100 + tmu.tmu_sample_timeout = 10.0 + + # Set debounce times for both start and stop inputs to the SAME value. + # Since start and stop sources are on the same channel, these must be + # equal. Setting different values would cause an error. A debounce time + # of 200ns filters out glitches shorter than 200ns. + tmu.tmu_start_input_debounce_time = 200e-9 + tmu.tmu_stop_input_debounce_time = 200e-9 + print("Same channel (DUTPin1): start debounce = 200 ns, stop debounce = 200 ns") + tmu.tmu_initiate() + measurement1 = tmu.tmu_fetch_averaged_measurement(10.0) + print(f"Period measurement with debounce: {measurement1:.6e} seconds") + tmu.tmu_abort() + + # =================================================================== + # Example 2: Different Channels - Independent Debounce Times + # =================================================================== + # When the TMU start source and stop source are configured to different + # channels, the start and stop debounce times can be configured + # independently. Each channel has its own physical input path, so each + # can have a different debounce filter setting. + print("\n--- Example 2: Different Channels Debounce (Independent Times) ---") + tmu.tmu_enabled = True + tmu.tmu_arm_type = nidigital.enums.TMUArmType.IMMEDIATE + + # Configure start and stop to DIFFERENT channels. This is a Skew + # measurement configuration. + tmu.tmu_start_source = dut_pin1 + tmu.tmu_start_source_event = nidigital.enums.TMUSourceEvent.VOL + tmu.tmu_start_source_event_polarity = nidigital.enums.TMUPolarity.RISING + tmu.tmu_stop_source = dut_pin2 + tmu.tmu_stop_source_event = nidigital.enums.TMUSourceEvent.VOL + tmu.tmu_stop_source_event_polarity = nidigital.enums.TMUPolarity.RISING + tmu.tmu_samples_to_acquire = 100 + tmu.tmu_sample_timeout = 10.0 + + # Set DIFFERENT debounce times for start and stop inputs. This is + # allowed because the start and stop sources are on different channels. + # Each channel's input path has its own independent debounce filter. + # Start debounce: 200ns - signal on DUTPin1 must be stable for 200ns. + # Stop debounce: 500ns - signal on DUTPin2 must be stable for 500ns. + tmu.tmu_start_input_debounce_time = 200e-9 + tmu.tmu_stop_input_debounce_time = 500e-9 + print("Different channels: start (DUTPin1) debounce = 200 ns, stop (DUTPin2) debounce = 500 ns") + tmu.tmu_initiate() + measurement2 = tmu.tmu_fetch_averaged_measurement(10.0) + print(f"Skew measurement with independent debounce: {measurement2:.6e} seconds") + tmu.tmu_abort() + + session.pins[""].selected_function = nidigital.enums.SelectedFunction.DISCONNECT + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/src/nidigital/examples/tmu_getting_started/PinMap.pinmap b/src/nidigital/examples/tmu_getting_started/PinMap.pinmap new file mode 100644 index 0000000000..aff6a135ed --- /dev/null +++ b/src/nidigital/examples/tmu_getting_started/PinMap.pinmap @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/nidigital/examples/tmu_getting_started/nidigital_tmu_getting_started.py b/src/nidigital/examples/tmu_getting_started/nidigital_tmu_getting_started.py new file mode 100644 index 0000000000..fd8fc4ad8b --- /dev/null +++ b/src/nidigital/examples/tmu_getting_started/nidigital_tmu_getting_started.py @@ -0,0 +1,111 @@ +""" +Demonstrates the minimal workflow to configure and perform a Time +Measurement Unit (TMU) measurement using the NI-Digital Pattern Driver API. +This example walks through every required step: + 1. Initialize the instrument session + 2. Load a pin map + 3. Configure voltage levels (VOL/VOH define TMU thresholds) + 4. Select a TMU from its instrument context + 5. Enable the TMU + 6. Configure a period measurement with Immediate arm + 7. Initiate the TMU acquisition + 8. Fetch the averaged measurement + 9. Abort the TMU and clean up + +This is the simplest possible TMU example. For arm type comparison, +multi-measurement workflows, debounce filtering, or multi-instrument sessions, +see the other TMU examples. + +Note: The DUT must be generating signals on the configured channel for the TMU +measurement to complete. In simulate mode, the driver returns simulated +measurement values. +""" + +from pathlib import Path + +import nidigital + + +RESOURCE_NAME = "PXI1Slot5" +OPTIONS = {"Simulate": True, "DriverSetup": {"Model": "6571"}} +PIN_MAP_PATH = Path(__file__).with_name("PinMap.pinmap") + + +def main(): + # Initial Device Values + dut_pin1 = "DUTPin1" + + # Step 1: Initialize the digital pattern instrument session. The reset + # input is TRUE to ensure the instrument starts in a known state. All + # channels are in a high-impedance state, and the I/O switches are open. + with nidigital.Session( + RESOURCE_NAME, id_query=True, reset_device=True, options=OPTIONS + ) as session: + # Step 2: Load the pin map. This allows referencing pin names (e.g., + # "DUTPin1") in the channel list of driver functions instead of raw + # channel numbers. + session.load_pin_map(str(PIN_MAP_PATH)) + + # Step 3: Configure voltage levels for all channels. The VOL and VOH + # levels define the thresholds used by the TMU for SOURCE_EVENT_VOL and + # SOURCE_EVENT_VOH events respectively. + session.configure_voltage_levels(0.0, 5.0, 0.5, 2.4, 0.0) + + # Step 4: Select TMU0 from its instrument context. This constructs the + # fully qualified driver context PXI1Slot5/tmu0. + instrument = session.instruments[RESOURCE_NAME] + tmu = instrument.time_measurement_units[0] + + # Step 5: Enable the TMU. TMUs are disabled by default and must be + # explicitly enabled before use. + tmu.tmu_enabled = True + + # Step 6: Configure a period measurement with Immediate arm. + # + # Arm Type: Immediate means the TMU begins looking for start/stop events + # as soon as tmu_initiate is called. No explicit arm event is required. + # + # Period measurement: Both start and stop sources are set to the same + # channel with the same event and polarity. This example measures the + # time between two consecutive rising-edge VOL threshold crossings, + # which is the signal period. + tmu.tmu_arm_type = nidigital.enums.TMUArmType.IMMEDIATE + + # Start source: DUTPin1, VOL threshold, Rising Edge + tmu.tmu_start_source = dut_pin1 + tmu.tmu_start_source_event = nidigital.enums.TMUSourceEvent.VOL + tmu.tmu_start_source_event_polarity = nidigital.enums.TMUPolarity.RISING + + # Stop source: same channel, same event, same polarity (period) + tmu.tmu_stop_source = dut_pin1 + tmu.tmu_stop_source_event = nidigital.enums.TMUSourceEvent.VOL + tmu.tmu_stop_source_event_polarity = nidigital.enums.TMUPolarity.RISING + + # Configure the number of samples to acquire and average. A higher + # sample count produces a more stable averaged measurement but takes + # longer to complete. + tmu.tmu_samples_to_acquire = 100 + + # Configure the per-sample timeout. If a single sample is not acquired + # within this time, the measurement fails. + tmu.tmu_sample_timeout = 10.0 + + # Step 7: Initiate the TMU acquisition (non-blocking). The TMU begins + # looking for start/stop events immediately. + tmu.tmu_initiate() + + # Step 8: Fetch the averaged measurement. This blocks until all + # configured samples have been acquired and averaged, or the timeout + # elapses. The result is in seconds. + period = tmu.tmu_fetch_averaged_measurement(10.0) + print(f"Period measurement: {period:.6e} seconds") + + # Step 9: Abort the TMU acquisition and clean up. + tmu.tmu_abort() + + # Disconnect all channels using programmable onboard switching. + session.pins[""].selected_function = nidigital.enums.SelectedFunction.DISCONNECT + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/src/nidigital/examples/tmu_measurement_types/PinMap.pinmap b/src/nidigital/examples/tmu_measurement_types/PinMap.pinmap new file mode 100644 index 0000000000..aff6a135ed --- /dev/null +++ b/src/nidigital/examples/tmu_measurement_types/PinMap.pinmap @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/nidigital/examples/tmu_measurement_types/nidigital_tmu_measurement_types.py b/src/nidigital/examples/tmu_measurement_types/nidigital_tmu_measurement_types.py new file mode 100644 index 0000000000..ed8e1023b7 --- /dev/null +++ b/src/nidigital/examples/tmu_measurement_types/nidigital_tmu_measurement_types.py @@ -0,0 +1,171 @@ +""" +Demonstrates how to perform common TMU measurement types using the +NI-Digital Pattern Driver API. This example shows three simple measurement +configurations: + - Period: time between two consecutive identical events on the same channel + (rising-edge to rising-edge) + - Duty Cycle High: time the signal spends above a threshold on a single + channel (rising-edge to falling-edge) + - Skew: time difference between the same event on two different channels + (inter-channel timing) +The TMU is not limited by these measurement configurations. + +Each measurement uses Edge arm to guarantee deterministic results. To use +Immediate arm instead, remove the edge arm configuration and change the arm +type to IMMEDIATE. + +Note: The DUT must be generating signals on the configured channels for TMU +measurements to complete. In simulate mode, the driver returns simulated +measurement values. +""" + +from pathlib import Path + +import nidigital + + +RESOURCE_NAME = "PXI1Slot5" +OPTIONS = {"Simulate": True, "DriverSetup": {"Model": "6571"}} +PIN_MAP_PATH = Path(__file__).with_name("PinMap.pinmap") + + +def main(): + # Initial Device Values + dut_pin1 = "DUTPin1" + dut_pin2 = "DUTPin2" + + # Initialize the digital pattern instrument session. The reset input is + # TRUE to ensure the instrument starts in a known state. + with nidigital.Session( + RESOURCE_NAME, id_query=True, reset_device=True, options=OPTIONS + ) as session: + # Load the pin map for the instrument to allow referencing of pin names + # in the channel list of select driver functions. + session.load_pin_map(str(PIN_MAP_PATH)) + + # Configure voltage levels for all channels. The VOL and VOH levels + # define the thresholds used by the TMU for SOURCE_EVENT_VOL and + # SOURCE_EVENT_VOH events respectively. + session.configure_voltage_levels(0.0, 5.0, 0.5, 2.4, 0.0) + + # Select TMU0 from its instrument context. + instrument = session.instruments[RESOURCE_NAME] + tmu = instrument.time_measurement_units[0] + + # Enable the TMU. + tmu.tmu_enabled = True + + # Configure sample count and timeout (shared by all measurements). + tmu.tmu_arm_type = nidigital.enums.TMUArmType.EDGE + tmu.tmu_samples_to_acquire = 100 + tmu.tmu_sample_timeout = 10.0 + + # =================================================================== + # Measurement 1: Period + # =================================================================== + # Measures the time between two consecutive identical events on the + # same channel. Both start and stop sources are configured to the same + # channel with the same event and polarity. + # + # Configuration: + # Start: DUTPin1, VOL threshold, Rising Edge + # Stop: DUTPin1, VOL threshold, Rising Edge (same as start) + # Arm: matches start/stop (Rising Edge at VOL on DUTPin1) + # + # The TMU captures the first rising-edge VOL crossing as the start, + # then the next one as the stop. The difference is one full period. + print("--- Measurement 1: Period ---") + + # Configure Edge arm. + # Start source: DUTPin1, VOL threshold, Rising Edge. + tmu.tmu_start_source = dut_pin1 + tmu.tmu_start_source_event = nidigital.enums.TMUSourceEvent.VOL + tmu.tmu_start_source_event_polarity = nidigital.enums.TMUPolarity.RISING + + # Stop source: same channel, same event/polarity for Period. + tmu.tmu_stop_source = dut_pin1 + tmu.tmu_stop_source_event = nidigital.enums.TMUSourceEvent.VOL + tmu.tmu_stop_source_event_polarity = nidigital.enums.TMUPolarity.RISING + + # Edge arm matches start/stop. + tmu.tmu_edge_arm_source = "DUTPin1" + tmu.tmu_edge_arm_source_event = nidigital.enums.TMUSourceEvent.VOL + tmu.tmu_edge_arm_polarity = nidigital.enums.TMUPolarity.RISING + tmu.tmu_initiate() + period_measurement = tmu.tmu_fetch_averaged_measurement(10.0) + print(f"Period: {period_measurement:.6e} seconds") + tmu.tmu_abort() + + # =================================================================== + # Measurement 2: Duty Cycle High + # =================================================================== + # Measures the high portion of a duty cycle on a single channel. The + # start event is the signal rising above a threshold, and the stop event + # is the signal falling below the same threshold. + # + # Configuration: + # Start: DUTPin1, VOH threshold, Rising Edge + # Stop: DUTPin1, VOH threshold, Falling Edge + # Arm: matches start (Rising Edge at VOH on DUTPin1) + # + # The arm event guarantees the rising edge is captured first, so the + # measurement is always the time the signal spends above VOH. + print("\n--- Measurement 2: Duty Cycle High ---") + + # Start source: DUTPin1, VOH threshold, Rising Edge. + tmu.tmu_start_source = dut_pin1 + tmu.tmu_start_source_event = nidigital.enums.TMUSourceEvent.VOH + tmu.tmu_start_source_event_polarity = nidigital.enums.TMUPolarity.RISING + + # Stop source: same channel, VOH threshold, Falling Edge. + tmu.tmu_stop_source = dut_pin1 + tmu.tmu_stop_source_event = nidigital.enums.TMUSourceEvent.VOH + tmu.tmu_stop_source_event_polarity = nidigital.enums.TMUPolarity.FALLING + tmu.tmu_edge_arm_source = "DUTPin1" + tmu.tmu_edge_arm_source_event = nidigital.enums.TMUSourceEvent.VOH + tmu.tmu_edge_arm_polarity = nidigital.enums.TMUPolarity.RISING + tmu.tmu_initiate() + duty_cycle_measurement = tmu.tmu_fetch_averaged_measurement(10.0) + print(f"Duty Cycle High: {duty_cycle_measurement:.6e} seconds") + tmu.tmu_abort() + + # =================================================================== + # Measurement 3: Skew + # =================================================================== + # Measures the time difference between the same event on two different + # channels. The start source is on one channel and the stop source is + # on a different channel. The result represents the inter-channel + # timing skew. + # + # Configuration: + # Start: DUTPin1, VOL threshold, Rising Edge + # Stop: DUTPin2, VOL threshold, Rising Edge + # Arm: matches start (Rising Edge at VOL on DUTPin1) + # + # The arm event on DUTPin1 guarantees the start event on DUTPin1 is + # captured first, then the TMU waits for the corresponding event on + # DUTPin2 as the stop. + print("\n--- Measurement 3: Skew ---") + + # Start source: DUTPin1, VOL threshold, Rising Edge. + tmu.tmu_start_source = dut_pin1 + tmu.tmu_start_source_event = nidigital.enums.TMUSourceEvent.VOL + tmu.tmu_start_source_event_polarity = nidigital.enums.TMUPolarity.RISING + + # Stop source: DUTPin2 (different channel), VOL threshold, Rising Edge. + tmu.tmu_stop_source = dut_pin2 + tmu.tmu_stop_source_event = nidigital.enums.TMUSourceEvent.VOL + tmu.tmu_stop_source_event_polarity = nidigital.enums.TMUPolarity.RISING + tmu.tmu_edge_arm_source = "DUTPin1" + tmu.tmu_edge_arm_source_event = nidigital.enums.TMUSourceEvent.VOL + tmu.tmu_edge_arm_polarity = nidigital.enums.TMUPolarity.RISING + tmu.tmu_initiate() + skew_measurement = tmu.tmu_fetch_averaged_measurement(10.0) + print(f"Skew: {skew_measurement:.6e} seconds") + tmu.tmu_abort() + + session.pins[""].selected_function = nidigital.enums.SelectedFunction.DISCONNECT + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/src/nidigital/examples/tmu_multi-instrument/PinMap.pinmap b/src/nidigital/examples/tmu_multi-instrument/PinMap.pinmap new file mode 100644 index 0000000000..6dba5a944e --- /dev/null +++ b/src/nidigital/examples/tmu_multi-instrument/PinMap.pinmap @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/nidigital/examples/tmu_multi-instrument/nidigital_tmu_multi-instrument.py b/src/nidigital/examples/tmu_multi-instrument/nidigital_tmu_multi-instrument.py new file mode 100644 index 0000000000..61cd384393 --- /dev/null +++ b/src/nidigital/examples/tmu_multi-instrument/nidigital_tmu_multi-instrument.py @@ -0,0 +1,177 @@ +""" +Demonstrates how to use the NI-Digital Pattern Driver API to work with TMU +resources in a multi-instrument session. This example shows: + - TMU naming conventions: each device has its own set of TMUs, and in + multi-instrument sessions, TMU contexts are device-qualified + - How GetDisabledTMUContexts returns device-qualified TMU context strings + so you can identify which device each TMU belongs to + - Using TMU resources across multiple devices in the same session + - TMU is per-device: each device has independent TMU hardware that can be + configured and operated separately + +Note: In simulate mode, the driver returns simulated measurement values. With +real hardware, each device's TMU operates independently on its own DIO channels. +""" + +from pathlib import Path + +import nidigital + + +RESOURCE_NAMES = "PXI1Slot5,PXI1Slot6" +OPTIONS = {"Simulate": True, "DriverSetup": {"Model": "6571"}} +PIN_MAP_PATH = Path(__file__).with_name("PinMap.pinmap") + + +def get_tmu_contexts_per_device(session): + """Discover available TMU contexts and return one TMU context per device.""" + + # GetDisabledTMUContexts returns device-qualified TMU context strings. + # Each TMU context includes the device resource name as a prefix, e.g.: + # "PXI1Slot5/tmu0" + # + # This tells you: + # - Which device each TMU belongs to + # - How many TMUs are available per device + # - The full context string needed to address each TMU + # + # TMU is per-device: each PXIe-6570/6571 instrument has its own TMU + # hardware. TMUs on different devices operate independently and can + # measure different channels simultaneously. + disabled_contexts = session.tmu_get_disabled_tmu_contexts() + if not disabled_contexts: + raise RuntimeError("No TMU contexts are available. Ensure the instruments support TMU.") + + print("TMU contexts in multi-instrument session (all initially disabled):") + print(f" {', '.join(disabled_contexts)}\n") + + # The Python API returns the driver contexts as a list of strings. Select + # one TMU per device; join() above is used only to display that list. + print("TMU resources per device:") + contexts_by_device = {} + for context in disabled_contexts: + print(f" {context}") + device, _, _ = context.partition("/tmu") + contexts_by_device.setdefault(device, context) + + if len(contexts_by_device) < 2: + raise RuntimeError("Two instruments with available TMUs are required.") + + tmu_context1, tmu_context2 = list(contexts_by_device.values())[:2] + print(f"Using TMU context 1: {tmu_context1}") + print(f"Using TMU context 2: {tmu_context2}\n") + return tmu_context1, tmu_context2 + + +def main(): + # Multi-instrument session: specify multiple device resource names separated + # by commas. Each device contributes its own TMU resources to the session. + + # Initialize the digital pattern instrument session with multiple devices. + # The session aggregates all devices and their TMU resources. + with nidigital.Session( + RESOURCE_NAMES, id_query=True, reset_device=True, options=OPTIONS + ) as session: + # Load the pin map for the multi-instrument configuration. The pin map + # defines how pins are mapped to channels across all instruments. + session.load_pin_map(str(PIN_MAP_PATH)) + + # Configure voltage levels for all channels. The VOL and VOH levels + # define the thresholds used by the TMU for SOURCE_EVENT_VOL and + # SOURCE_EVENT_VOH events respectively. + session.configure_voltage_levels(0.0, 5.0, 0.5, 2.4, 0.0) + + # Discover TMU Resources. + tmu_context1, tmu_context2 = get_tmu_contexts_per_device(session) + + # =================================================================== + # Configure TMUs on Different Devices + # =================================================================== + # In a multi-instrument session, you must use device-qualified TMU + # context strings when setting attributes and calling TMU functions. + # + # Here we configure one TMU on each device to perform independent + # measurements. Each device's TMU can only measure channels connected + # to that same device. + + # --- Device 1: Period measurement on DUTPin1 --- + # DUTPin1 is connected to the first device's channel 0 (per the pin + # map). We use the first TMU context from GetDisabledTMUContexts. + print(f"--- Device 1 ({tmu_context1}): Period on DUTPin1 ---") + device1, tmu_id1 = tmu_context1.split("/", maxsplit=1) + tmu1 = session.instruments[device1].time_measurement_units[tmu_id1] + tmu1.tmu_enabled = True + tmu1.tmu_arm_type = nidigital.enums.TMUArmType.IMMEDIATE + tmu1.tmu_start_source = "DUTPin1" + tmu1.tmu_start_source_event = nidigital.enums.TMUSourceEvent.VOL + tmu1.tmu_start_source_event_polarity = nidigital.enums.TMUPolarity.RISING + tmu1.tmu_stop_source = "DUTPin1" + tmu1.tmu_stop_source_event = nidigital.enums.TMUSourceEvent.VOL + tmu1.tmu_stop_source_event_polarity = nidigital.enums.TMUPolarity.RISING + tmu1.tmu_samples_to_acquire = 100 + tmu1.tmu_sample_timeout = 10.0 + + # --- Device 2: Period measurement on DUTPin3 --- + # DUTPin3 is connected to the second device's channel (per the pin map). + # We use the second TMU context from GetDisabledTMUContexts. Each + # device's TMU operates independently, so both measurements can be + # configured and run in the same session. + print(f"--- Device 2 ({tmu_context2}): Period on DUTPin3 ---") + device2, tmu_id2 = tmu_context2.split("/", maxsplit=1) + tmu2 = session.instruments[device2].time_measurement_units[tmu_id2] + tmu2.tmu_enabled = True + tmu2.tmu_arm_type = nidigital.enums.TMUArmType.IMMEDIATE + tmu2.tmu_start_source = "DUTPin3" + tmu2.tmu_start_source_event = nidigital.enums.TMUSourceEvent.VOL + tmu2.tmu_start_source_event_polarity = nidigital.enums.TMUPolarity.RISING + tmu2.tmu_stop_source = "DUTPin3" + tmu2.tmu_stop_source_event = nidigital.enums.TMUSourceEvent.VOL + tmu2.tmu_stop_source_event_polarity = nidigital.enums.TMUPolarity.RISING + tmu2.tmu_samples_to_acquire = 100 + tmu2.tmu_sample_timeout = 10.0 + + # This code snippet demonstrates that GetDisabledTMUContexts returns + # only the context strings for TMUs that have not been enabled. Because + # two TMUs have been enabled, one on each instrument, + # GetDisabledTMUContexts returns two fewer context strings. + # + # Note: This call triggers attribute verification for all TMUs. If any + # TMU has an invalid configuration, it will appear as disabled. + print("\n--- Checking disabled TMUs after configuration ---") + remaining_contexts = session.tmu_get_disabled_tmu_contexts() + if remaining_contexts: + print(f"Still disabled TMU contexts:\n {', '.join(remaining_contexts)}") + else: + print("All TMUs are enabled.") + + # =================================================================== + # Initiate and Fetch Measurements on Both Devices + # =================================================================== + # Each device's TMU can be initiated independently. The TMU hardware on + # each device operates in parallel. + print("\n--- Initiating measurements on both devices ---") + + # Initiate TMU on device 1. + tmu1.tmu_initiate() + + # Initiate TMU on device 2. + tmu2.tmu_initiate() + + # Fetch measurement from device 1. + measurement1 = tmu1.tmu_fetch_averaged_measurement(10.0) + measurement2 = tmu2.tmu_fetch_averaged_measurement(10.0) + print(f"{tmu_context1} Period: {measurement1:.6e} seconds") + + # Fetch measurement from device 2. + print(f"{tmu_context2} Period: {measurement2:.6e} seconds") + + # Abort both TMU acquisitions. + tmu1.tmu_abort() + tmu2.tmu_abort() + + # Disconnect all channels using programmable onboard switching. + session.pins[""].selected_function = nidigital.enums.SelectedFunction.DISCONNECT + + +if __name__ == "__main__": + main() \ No newline at end of file From efb67718aaa5d74550b915ba1b3154710897ec5e Mon Sep 17 00:00:00 2001 From: elebel-emerson Date: Tue, 20 Oct 2026 13:35:49 -0500 Subject: [PATCH 2/2] Add whitespace at end of file --- src/nidigital/examples/tmu_arm_types/nidigital_tmu_arm_types.py | 2 +- src/nidigital/examples/tmu_debounce/nidigital_tmu_debounce.py | 2 +- .../tmu_getting_started/nidigital_tmu_getting_started.py | 2 +- .../tmu_measurement_types/nidigital_tmu_measurement_types.py | 2 +- .../tmu_multi-instrument/nidigital_tmu_multi-instrument.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/nidigital/examples/tmu_arm_types/nidigital_tmu_arm_types.py b/src/nidigital/examples/tmu_arm_types/nidigital_tmu_arm_types.py index cda26482e7..0a16a0e109 100644 --- a/src/nidigital/examples/tmu_arm_types/nidigital_tmu_arm_types.py +++ b/src/nidigital/examples/tmu_arm_types/nidigital_tmu_arm_types.py @@ -145,4 +145,4 @@ def main(): if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/src/nidigital/examples/tmu_debounce/nidigital_tmu_debounce.py b/src/nidigital/examples/tmu_debounce/nidigital_tmu_debounce.py index 0a946709f0..9e9572cd60 100644 --- a/src/nidigital/examples/tmu_debounce/nidigital_tmu_debounce.py +++ b/src/nidigital/examples/tmu_debounce/nidigital_tmu_debounce.py @@ -123,4 +123,4 @@ def main(): if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/src/nidigital/examples/tmu_getting_started/nidigital_tmu_getting_started.py b/src/nidigital/examples/tmu_getting_started/nidigital_tmu_getting_started.py index fd8fc4ad8b..032b3982f7 100644 --- a/src/nidigital/examples/tmu_getting_started/nidigital_tmu_getting_started.py +++ b/src/nidigital/examples/tmu_getting_started/nidigital_tmu_getting_started.py @@ -108,4 +108,4 @@ def main(): if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/src/nidigital/examples/tmu_measurement_types/nidigital_tmu_measurement_types.py b/src/nidigital/examples/tmu_measurement_types/nidigital_tmu_measurement_types.py index ed8e1023b7..a012849cf3 100644 --- a/src/nidigital/examples/tmu_measurement_types/nidigital_tmu_measurement_types.py +++ b/src/nidigital/examples/tmu_measurement_types/nidigital_tmu_measurement_types.py @@ -168,4 +168,4 @@ def main(): if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/src/nidigital/examples/tmu_multi-instrument/nidigital_tmu_multi-instrument.py b/src/nidigital/examples/tmu_multi-instrument/nidigital_tmu_multi-instrument.py index 61cd384393..309f5b9b1a 100644 --- a/src/nidigital/examples/tmu_multi-instrument/nidigital_tmu_multi-instrument.py +++ b/src/nidigital/examples/tmu_multi-instrument/nidigital_tmu_multi-instrument.py @@ -174,4 +174,4 @@ def main(): if __name__ == "__main__": - main() \ No newline at end of file + main()