"""Library to generate a waveform and send it to the DAC.""" import time from scipy import signal import numpy as np from mcculw import ul from mcculw.enums import ScanOptions, FunctionType, Status import util from ao import AnalogOutputProps from mcculw.ul import ULError use_device_detection = True def run_example(): """Generate a waveform and send it to the DAC.""" board_num = 0 if use_device_detection: ul.ignore_instacal() if not util.config_first_detected_device(board_num): print("Could not find device.") return ao_props = AnalogOutputProps(board_num) if ao_props.num_chans < 1: util.print_unsupported_example(board_num) return low_chan = 0 high_chan = 1 # min(3, ao_props.num_chans - 1) num_chans = high_chan - low_chan + 1 frequency = 100 # period per second (Hz) num_periods = 10 sample_rate = 5000 # samples per second points_per_channel = int(sample_rate*num_periods/frequency) total_count = points_per_channel * num_chans # only range available for this device is ULRange.BIP10VOLTS (±10 volts) ao_range = ao_props.available_ranges[0] # Allocate a buffer for the scan memhandle = ul.win_buf_alloc(total_count) # Convert the memhandle to a ctypes array # Note: the ctypes array will no longer be valid after win_buf_free # is called. # A copy of the buffer can be created using win_buf_to_array # before the memory is freed. The copy can be used at any time. ctypes_array = util.memhandle_as_ctypes_array(memhandle) # Check if the buffer was successfully allocated if not memhandle: print("Failed to allocate memory.") return print('generating data at', time.monotonic()) generate_data(ctypes_array, num_chans, sample_rate, points_per_channel, frequency) try: # Start the scan print('start scan', time.monotonic()) ul.a_out_scan( board_num, low_chan, high_chan, total_count, sample_rate, ao_range, memhandle, ScanOptions.BACKGROUND) print('scan has been started at', time.monotonic()) # Wait for the scan to complete print("Waiting for output scan to complete...", end="", flush=True) status = Status.RUNNING while status != Status.IDLE: print(".", end="", flush=True) # Slow down the status check so as not to flood the CPU time.sleep(0.2) status, _, _ = ul.get_status( board_num, FunctionType.AOFUNCTION) print("") print("Scan completed successfully.", time.monotonic()) except ULError as e: util.print_ul_error(e) finally: # Free the buffer in a finally block to prevent errors from causing # a memory leak. ul.win_buf_free(memhandle) if use_device_detection: ul.release_daq_device(board_num) def generate_data(ctypes_array, num_chans, sample_rate, points_per_channel, frequency): """Generate waveform data. The USB-3101FS D/A converter has a range as follows: -10.0V = 2144 0V = 32768 +10.0V = 63392 """ amplitude = 30000 y_offset = 32768 + amplitude/2 # generate an array of time values # start time is 0 seconds and end time is points/(points/seconds) = seconds t = np.linspace(0, points_per_channel/sample_rate, points_per_channel) a = y_offset + amplitude*signal.sawtooth(2 * np.pi * frequency * t, 1)/2 b = y_offset + amplitude*signal.sawtooth(2 * np.pi * frequency * t, 0)/2 # always make sure the last point is 0V to ensure we don't leave the analog output turned on a[-1] = 32768 b[-1] = 32768 # create a numpy array from a ctypes POINTER. This numpy array shares memory with the ctypes # POINTER so changing the numpy array is the same as changing the ctypes POINTER. numpy_arr = np.ctypeslib.as_array(ctypes_array, shape=(num_chans*points_per_channel,)) # interleave the two waveforms into the POINTER # a starts from 0 and increments by 2 # b starts from 1 and increments by 2 numpy_arr[0::2] = a numpy_arr[1::2] = b if __name__ == '__main__': run_example()