Multifunction DAQ

cancel
Showing results for 
Search instead for 
Did you mean: 

Repeatedly starting and stopping analog input task with registered callback causes BSOD (NI USB 6001)

Hello!

 

I've originally posted this issue on the nidaqmx-python github (https://github.com/ni/nidaqmx-python/issues/637). I was advised by zhindes to post here.

Here goes the original message:


Hello!

I've been having this problem for some time now, but I finally managed to recreate it reliably. Before, I would get it randomly, with multiple days without it, and then some days getting it multiple times.

  • I am using NI USB-6001 card.
  • OS is Windows 11 Pro, Version 10.0.22631 Build 22631
  • NI-DAQmx version 2024 Q3.1 (though I recently updated while trying to fix the issue.)
  • nidaqmx-python version 1.0.1 (was also experiencing problem with 0.9.0)
  • with python 3.11.7

Below code runs for a couple of seconds before windows crashes with a blue screen of death:

 

 

import nidaqmx
import nidaqmx.stream_readers
import numpy as np

device = nidaqmx.system.System().local().devices[0]

task = nidaqmx.Task("analog_input")
task.ai_channels.add_ai_voltage_chan(
    physical_channel=",".join([device.ai_physical_chans.channel_names[0], device.ai_physical_chans.channel_names[1]]),
    name_to_assign_to_channel="joystick",
    terminal_config=nidaqmx.constants.TerminalConfiguration.DIFF
)
task.ai_channels.add_ai_voltage_chan(
    physical_channel=device.ai_physical_chans[3].name,
    name_to_assign_to_channel="touch",
    terminal_config=nidaqmx.constants.TerminalConfiguration.RSE
)
task.timing.cfg_samp_clk_timing(rate=150, sample_mode=nidaqmx.constants.AcquisitionType.CONTINUOUS)

def _analog_samples_acquired_callback(task_handle, every_n_samples_event_type,
        number_of_samples, callback_data):
    try:
        analog_reader.read_many_sample(np.empty((1, 3)), number_of_samples, timeout=0) # Value not needed for demonstration
    except nidaqmx.DaqReadError as daq_read_error:
        # Check if task has already been stopped. If not, reraise error (else ignore).
        if not task.is_task_done():
            raise daq_read_error
        return -1 # return value of 1 indicates that error was ignored

    return 0

analog_reader = nidaqmx.stream_readers.AnalogMultiChannelReader(task.in_stream)
task.register_every_n_samples_acquired_into_buffer_event(1, _analog_samples_acquired_callback)

try:
    while True:
        print("START")
        task.start()
        print("STOP")
        task.stop()
finally:
    print("DONE!")
    task.close()

 

 

The BSOD check code is either a 0x00000139 (KERNEL_SECURITY_CHECK_FAILURE) or 0x00000044 (MULTIPLE_IRP_COMPLETE_REQUESTS). Which one it is seems to be random (at least I don't know what causes the difference).
My original code was stopping and immediately restarting the task regularly. This is to ensure synchronization of the recording start time with an external signal, which happens every 4 to 9 seconds.
I'm attaching the contents of my 'C:\Windows\Minidump' folder as zip archive.

Any ideas on what the problem might be?

Thank you!

 


Suggestions by zhindes that I have tried, to no avail:

  • I have tried setting 'ai_usb_xfer_req_count' to 1 for all ai_channels (though it seems that it was 1 by default).
  • I also tried reducing the 'ai_usb_xfer_req_size'. I tried all powers of two from 1024 (which seems to be the minimum) up to 32768

Windows is not in a VM. The card is connected to a 'Intel(R) USB 3.1 eXtensible-Hostcontroller - 1.10 (Microsoft)'. A screenshot of the corresponding section in the device manager can be found below.

I have also tried running the same script on another, more powerful computer, with the same outcome. But I've only gotten 'KERNEL_SECURITY_CHECK_FAILED' errors in about 10 tries. I can also give additional info on that machine if needed.

Thank you!

 

Screenshot 2024-11-05 120047.png

0 Kudos
Message 1 of 3
(323 Views)

Hello  dnzrgn,

Have you been able to solve the problem? I have similar crashes, but I don't use Python for data acquisition but Labview.

Many thanks in advance

0 Kudos
Message 2 of 3
(42 Views)

Hi MCF_Patrick,

I abandoned the callback function registration via

 

register_every_n_samples_acquired_into_buffer_event

 

entirely. Instead, I created my own python thread just to read the analog data into my recordings array. I don't know if that's any help for you since you are using LabView, sorry.


For anyone stumbling upon this in the future: For my usecase (relatively low input frequency), using a python thread is sufficient. Though you have to jump through some hoops to make it work. Below is a sample code. BUT, I had to reduce the code and remove sensitive information. I currently don't have access to the hardware, so I cannot test if this sample code is working. It's just to convey the general idea. Use at your own risk.

 

 

 

import nidaqmx
import nidaqmx.stream_readers
import numpy as np
import threading
from time import sleep

INPUT_FREQ = 150, # [Hz]
INPUT_SAMPLE_INTERVAL = 1 # INPUT_FREQ / INPUT_SAMPLE_INTERVAL = SAMPLE_FREQ [Hz]
MAX_RECORDING_TIME = 120*60, # [sec]
READ_AI_TIMEOUT = 2, # [sec]

device = nidaqmx.system.System().local().devices[0]

task = nidaqmx.Task("analog_input")
task.ai_channels.add_ai_voltage_chan(
    physical_channel=",".join([device.ai_physical_chans.channel_names[0], device.ai_physical_chans.channel_names[1]]),
    name_to_assign_to_channel="joystick",
    terminal_config=nidaqmx.constants.TerminalConfiguration.DIFF
)
task.ai_channels.add_ai_voltage_chan(
    physical_channel=device.ai_physical_chans[3].name,
    name_to_assign_to_channel="touch",
    terminal_config=nidaqmx.constants.TerminalConfiguration.RSE
)
task.timing.cfg_samp_clk_timing(rate=150, sample_mode=nidaqmx.constants.AcquisitionType.CONTINUOUS)
_ai_in_stream = task.in_stream

_buffer_ai = np.empty((task.number_of_channels, INPUT_SAMPLE_INTERVAL), dtype=np.float64)
_recording_size = 150*(MAX_RECORDING_TIME + (60 * 5))
_recording = np.empty((task.number_of_channels, _recording_size), dtype=np.float64)
_index_recording = 0

def _job_ai(lock: threading.Lock, stop_event: threading.Event):
    global _index_recording

    reader = nidaqmx.stream_readers.AnalogMultiChannelReader(_ai_in_stream)
    new_sample = False

    while True:
        if stop_event.is_set():
            stop_event.clear()
            break
        # Give main thread chance to acquire lock
        sleep(0.001)
        lock.acquire()
        try:
            if task.is_grabbing:
                reader.read_many_sample(_buffer_ai, INPUT_SAMPLE_INTERVAL, timeout=READ_AI_TIMEOUT)
                new_sample = True
        finally:
            lock.release()
        if new_sample:
            _recording[..., _index_recording:_index_recording+INPUT_SAMPLE_INTERVAL] = _buffer_ai[:]
            _index_recording += INPUT_SAMPLE_INTERVAL
        new_sample = False

def _decorate_task_start_stop_to_use_lock(task: nidaqmx.Task, lock: threading.Lock):
    """Function that decorates given task. Decorated method acquires given lock while calling the start and stop methods of the task."""

    from functools import wraps
    from types import MethodType

    task.is_grabbing = False
    inner_start = task.start
    inner_stop = task.stop

    @wraps(inner_start)
    def start_with_lock(self):
        lock.acquire()
        try:
            inner_start()
            self.is_grabbing = True
        finally:
            lock.release()
    @wraps(inner_stop)
    def stop_with_lock(self):
        lock.acquire()
        try:
            inner_stop()
            self.is_grabbing = False
        finally:
            lock.release()
    
    task.start = MethodType(start_with_lock, task)
    task.stop = MethodType(stop_with_lock, task)

_event_stop_ai = threading.Event()
_lock_ai = threading.Lock()
_thread_ai = threading.Thread(target=_job_ai, args=(_lock_ai, _event_stop_ai), daemon=True)

_decorate_task_start_stop_to_use_lock(task, _lock_ai)

task.start()
_thread_ai.start()

try:
    while True:
        print("START")
        task.start()
        print("STOP")
        task.stop()
finally:
    _event_stop_ai.set()
    task.close()
    print("DONE!")

 

I may have overcomplicated it a little bit. But since it was working, I was content with it.

The idea is to use a lock to ensure that the task is not started or stopped while data is being read from the buffer, and vice versa.
It was also working on a normal office laptop. So if you don't have any other resource intensive processes running on the computer, it should be possible to increase the input frequency quite a bit.

Best

0 Kudos
Message 3 of 3
(26 Views)