Using DaqInfo
with ChannelBuffer
In this tutorial, we'll demonstrate how to use the DaqInfo
object (created in a previous tutorial) alongside the ChannelBuffer
module. This will allow us to scale and store acquired data for easy access and further processing.
Overview
The key steps to follow in this tutorial are:
- Load the
daqinfo.toml
file and create aDaqInfo
object. - Create an
AcqBufferPool
object, which will hold anAcqBuffer
for each channel. - Fill the buffer with data from the acquisition process.
- Read back and display the stored data.
- Extra: how to perform cyclical calculation
Prerequisites: Ensure you’ve completed the previous tutorial and saved your DaqInfo
configuration as daqinfo.toml
.
Getting started
This example demonstrates how to acquire data, store it in a buffer, and retrieve it for visualization.
-
Connect the Function Generator:
Set up the wiring as shown in the diagram:
Connect the "inner" part of the BNC connector (signal) to the A1 pin, and the GND to the GND pin.
-
Configure the Function Generator:
Adjust the function generator to output a sine wave signal with the following settings:
- Waveform: Sine
- Frequency: 50 Hz
- Amplitude: 2 Vpp
- Offset: 1.5 V
Note
The Arduino Due inputs cannot handle negative voltages, so an offset must be applied to the sine wave.
-
Enable the Function Generator Output
Make sure the function generator is actively outputting the signal.
-
Prepare the script for reading the data
from daqopen.channelbuffer import AcqBufferPool from daqopen.daqinfo import DaqInfo from daqopen.duedaq import DueDaq import matplotlib.pyplot as plt import tomllib # Load DaqInfo from daqinfo.toml with open("daqinfo.toml", "rb") as f: daq_info_dict = tomllib.load(f) # Create DaqInfo Object from dict my_daq_info = DaqInfo.from_dict(daq_info_dict) # Create an instance of with properties from the DaqInfo myDaq = DueDaq(channels=my_daq_info.ai_pin_name.values(), # Initialize all enabled channels from DaqInfo samplerate=my_daq_info.board.samplerate) # Set the wanted samplerate # Create the Buffer with daqinfo acq_buffer = AcqBufferPool(daq_info=my_daq_info, data_columns=myDaq.data_columns) # Provide info about the Channel-Column map # Start the Acquisition myDaq.start_acquisition() # Read some data packages and insert the data in the buffer for i in range(20): data = myDaq.read_data() acq_buffer.put_data_with_samplerate(data, myDaq.samplerate) # put data to buffer # Stop acquisition myDaq.stop_acquisition() # Read back 0.2s of data and plot ts = acq_buffer.time.read_data_by_index(0, int(myDaq.samplerate*0.2)) for channel_name, channel_buffer in acq_buffer.channel.items(): scaled_data = channel_buffer.read_data_by_index(0, int(myDaq.samplerate*0.2)) plt.plot(ts/1e6, scaled_data, label=channel_name) # Plot data plt.legend() plt.grid() plt.show()
Explanation of the Script:
- Loading
daqinfo.toml
: The configuration filedaqinfo.toml
is loaded usingtomllib.load(f)
. - Creating
DaqInfo
: TheDaqInfo
object is instantiated using the dictionary from the TOML file withDaqInfo.from_dict()
. - Creating
DueDaq
: TheDueDaq
object is instantiated with values from theDaqInfo
this time. Here we set thechannels
to be read and the wantedsamplerate
per channel. - Setting Up the Buffer: The
AcqBufferPool
is initialized with theDaqInfo
object as well as a dictionary defining the channels todata_columns
index map. This buffer will hold the acquired data, with the correct scaling and buffer width based on the configuration. - Starting Data Acquisition: The
DueDaq.start_acquisition()
method manages the hardware and begins the acquisition process. - Inserting Data into the Buffer: Data is read in packets from the hardware and stored in the buffer. The
put_data_with_samplerate
method ensures that timestamps are aligned with the acquisition rate. - Reading Back Data: After acquisition, the first 200ms of data is retrieved from each activated channel using the buffer’s channel items.
- Visualizing the Data: The retrieved data is plotted, showing the sine wave signal over a 200ms period.
- Loading
-
Run the script
Once the script is executed, you should see a sine wave displayed, representing the signal from 0 to 0.2 seconds:
Notice that there are exactly 10 cycles of the 50 Hz signal within the 0.2s window, confirming the correct sampling rate.
-
Enable a second channel
Until now, we only worked with the channel A0. Let's also use a second channel. To do so, we simply add a second channel section in the daqinfo.toml by copying the first one and replacing A0 with A1:
# This is a Arduino Due DAQ Configuration File [board] type = "duedaq" samplerate = 50000 # Samplerate to be set [channel.A0] # This is the first channel gain= 7.94998E-04 offset = -9.77193E-03 delay = 0 unit = "V" [channel.A1] # This is the second channel gain= 7.94998E-04 offset = -9.77193E-03 delay = 0 unit = "V"
-
Run the script again
Because there is no signal applied to the channel A1, you see a "ghost" signal due to capacitance of the input stage.
This tutorial provides a clear example of how to acquire, store, and visualize signal data using the DaqInfo
and ChannelBuffer
modules. Make sure to adapt the code and settings as needed for your specific use case.
Cyclical calculation
In this section, we will use the same setup as before to perform a cyclical calculation every 0.2 seconds. The challenge here is to acquire the signal in an infinite loop, wait for the next 200 ms of data to be gathered, and then perform the necessary calculation. One advantage of this approach is that it’s easy to change the interval, as we can directly read the required data from the buffer without needing to track individual data packets.
-
Script Adaption
from daqopen.channelbuffer import AcqBufferPool from daqopen.daqinfo import DaqInfo from daqopen.duedaq import DueDaq from daqopen.helper import GracefulKiller import matplotlib.pyplot as plt import tomllib # Load DaqInfo from daqinfo.toml with open("daqinfo.toml", "rb") as f: daq_info_dict = tomllib.load(f) # Create DaqInfo Object from dict my_daq_info = DaqInfo.from_dict(daq_info_dict) # Create an instance of with properties from the DaqInfo myDaq = DueDaq(channels=my_daq_info.ai_pin_name.values(), # Initialize all enabled channels from DaqInfo samplerate=my_daq_info.board.samplerate) # Set the wanted samplerate # Create the Buffer with daqinfo acq_buffer = AcqBufferPool(daq_info=my_daq_info, data_columns=myDaq.data_columns) # Provide info about the Channel-Column map # Start the Acquisition myDaq.start_acquisition() # Create an exit handler to gracefully terminate the script app_killer = GracefulKiller() # Define the calculation interval calc_interval_samples = int(0.2 * myDaq.samplerate) # number of samples for each 200ms interval previous_calc_sidx = 0 # stores the previous calculation's sample index # Infinite loop for data acquisition and calculations while not app_killer.kill_now: # Read one block of data from the DAQ device data = myDaq.read_data() # Insert the data into the acquisition buffer acq_buffer.put_data_with_samplerate(data, myDaq.samplerate) # Check if enough samples are available for the next calculation while (acq_buffer.time.sample_count - calc_interval_samples) > previous_calc_sidx: # Read a chunk of data corresponding to the interval size for channel_name, channel_buffer in acq_buffer.channel.items(): data = channel_buffer.read_data_by_index(previous_calc_sidx, previous_calc_sidx + calc_interval_samples) print(channel_name, data.mean(), my_daq_info.channel[channel_name].unit) # Perform the calculation previous_calc_sidx += calc_interval_samples # Update the index for the next cycle # Stop acquisition when the loop ends myDaq.stop_acquisition()
Explanation of the Script:
-
Graceful Exit Handling: The
GracefulKiller
class enables the script to exit cleanly. It allows you to stop the infinite loop and shut down data acquisition in an orderly manner (e.g., when pressingCtrl+C
). -
Defining the Calculation Interval: We define the calculation interval by converting 0.2 seconds into a number of samples using
calc_interval_samples
. Theprevious_calc_sidx
variable helps track where the last calculation ended so that the next calculation starts from the right place. -
Main Acquisition and Calculation Loop: The script runs an infinite loop where:
- Data is read from the DAQ device.
- The read samples are inserted into the acquisition buffer.
- The buffer is checked to see if enough data is available for the next calculation. If so, data is retrieved in chunks of
calc_interval_samples
. - The mean value of each channel is calculated and printed.
- The index is updated so that the next calculation uses the correct data.
Timing Considerations
Since the calculation interval is determined by an integer number of samples, the interval may not be perfectly accurate. We will address this in a future tutorial, explaining how to manage such discrepancies.
CPU Time Usage
Be mindful of operations that consume a lot of CPU time. The DAQ device readout must be completed within the buffer size’s time limit to avoid acquisition errors. A potential solution is to decouple the acquisition process from other tasks using separate processes or queues (e.g., ZMQ).
-
-
Run the script
To run the script, simply execute it from your Python environment. Ensure the DAQ device is connected and properly configured before starting the acquisition.
-
View Output