Skip to content

Commit e647a6c

Browse files
committed
Rename AnalogAcquisitionHandler to new oscilloscope.Oscilloscope
1 parent ff3bfba commit e647a6c

File tree

3 files changed

+317
-309
lines changed

3 files changed

+317
-309
lines changed

PSL/achan.py

Lines changed: 1 addition & 305 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
import time
2-
from typing import List, Tuple, Union
1+
from typing import List, Union
32

43
import numpy as np
54

6-
from PSL.analyticsClass import analyticsClass
75
import PSL.commands_proto as CP
86
from PSL import packet_handler
97

@@ -172,305 +170,3 @@ def unscale(self, voltage: float) -> int:
172170
level = np.clip(level, 0, self._resolution)
173171
level = np.round(level)
174172
return int(level)
175-
176-
177-
class AnalogAcquisitionHandler:
178-
"""
179-
"""
180-
181-
MAX_SAMPLES = 10000
182-
CH234 = ["CH2", "CH3", "MIC"]
183-
184-
def __init__(self, connection: packet_handler.Handler = None):
185-
self.connection = packet_handler.Handler() if connection is None else connection
186-
self.channels = {a: AnalogInput(a, self.connection) for a in ANALOG_CHANNELS}
187-
self.channel_one_map = "CH1"
188-
self._trigger_voltage = 0
189-
self.trigger_enabled = False
190-
self._trigger_channel = "CH1"
191-
self.data_splitting = CP.DATA_SPLITTING
192-
193-
def capture(self, channels: int, samples: int, timegap: float,) -> np.ndarray:
194-
"""Blocking call that fetches an oscilloscope trace from the specified input channels.
195-
196-
Parameters
197-
----------
198-
channels : {1, 2, 4}
199-
Number of channels to sample from simultaneously. By default, samples are
200-
captured from CH1, CH2, CH3 and MIC. CH1 can be remapped to any other
201-
channel (CH2, CH3, MIC, CAP, SEN, AN8) by setting the channel_one_map
202-
attribute of the AnalogAcquisitionHandler instance to the desired channel.
203-
samples : int
204-
Number of samples to fetch. Maximum 10000 divided by number of channels.
205-
timegap : float
206-
Timegap between samples in microseconds. Will be rounded to the closest
207-
1 / 8 µs. The minimum timegap depends on the type of measurement:
208-
When sampling a single, untriggered channel with 10 bits of resolution,
209-
the timegap must be exactly 0.5 µs (2 Msps).
210-
When sampling a single channel with 12 bits of resolution, the timegap
211-
must be 2 µs or greater (500 ksps).
212-
When sampling two or more channels, the timegap must be 0.875 µs or
213-
greater (1.1 Msps).
214-
215-
Example
216-
-------
217-
>>> from PSL import achan
218-
>>> analog_channels = achan.AnalogAcquisitionHandler()
219-
>>> x, y = analog_channels.capture(1, 3200, 1)
220-
221-
Returns
222-
-------
223-
numpy.ndarray
224-
(:channels:+1)-dimensional array with timestamps in the first dimension
225-
and corresponding voltages in the following dimensions.
226-
227-
Raises
228-
------
229-
ValueError
230-
If :channels: > 4 or
231-
:samples: > 10000 / :channels:, or
232-
:channel_one_map: is not one of CH1, CH2, CH3, MIC, CAP, SEN, AN8, or
233-
:timegap: is too low.
234-
"""
235-
self.capture_nonblocking(channels, samples, timegap)
236-
time.sleep(1e-6 * samples * timegap + 0.01)
237-
238-
while not self.progress()[0]:
239-
pass
240-
241-
xy = np.zeros([channels + 1, samples])
242-
xy[0] = timegap * np.arange(samples)
243-
active_channels = [
244-
self.channels[k] for k in ([self.channel_one_map] + self.CH234)[:channels]
245-
]
246-
for e, c in enumerate(active_channels):
247-
xy[e + 1] = c.scale(self.fetch_data(c.buffer, samples))
248-
249-
return xy
250-
251-
def capture_nonblocking(self, channels: int, samples: int, timegap: float):
252-
"""Tell the pslab to start sampling the specified input channels.
253-
254-
This method is identical to :meth:`capture <PSL.achan.AnalogAcquisitionHandler.capture>`,
255-
except it does not block while the samples are being captured. Collected
256-
samples must be manually fetched by calling :meth:`fetch_data <PSL.achan.AnalogAcquisitionHandler.fetch_data>`.
257-
258-
Parameters
259-
----------
260-
See :meth:`capture <PSL.achan.AnalogAcquisitionHandler.capture>`.
261-
262-
Example
263-
-------
264-
>>> import numpy as np
265-
>>> from PSL import achan
266-
>>> analog_channels = achan.AnalogAcquisitionHandler()
267-
>>> analog_channels.capture_nonblocking(1, 3200, 1)
268-
>>> x = 1 * np.arange(3200)
269-
>>> y = analog_channels.fetch_data(0, 3200)
270-
271-
Raises
272-
------
273-
See :meth:`capture <PSL.achan.AnalogAcquisitionHandler.capture>`.
274-
"""
275-
self._check_args(channels, samples, timegap)
276-
timegap = int(timegap * 8) / 8
277-
self._capture(channels, samples, timegap)
278-
279-
def _check_args(self, channels: int, samples: int, timegap: float):
280-
if channels not in (1, 2, 4):
281-
raise ValueError("Number of channels to sample must be 1, 2, or 4.")
282-
283-
max_samples = self.MAX_SAMPLES // channels
284-
if not 0 < samples <= max_samples:
285-
e1 = f"Cannot collect more than {max_samples} when sampling from "
286-
e2 = f"{channels} channels."
287-
raise ValueError(e1 + e2)
288-
289-
min_timegap = 0.5 + 0.375 * (channels > 1 or self.trigger_enabled)
290-
if timegap < min_timegap:
291-
raise ValueError(f"timegap must be at least {min_timegap}.")
292-
293-
if self.channel_one_map not in self.channels:
294-
e1 = f"{self.channel_one_map} is not a valid channel. "
295-
e2 = f"Valid channels are {list(self.channels.keys())}."
296-
raise ValueError(e1 + e2)
297-
298-
def _capture(self, channels: int, samples: int, timegap: float):
299-
chosa = self.channels[self.channel_one_map].chosa
300-
self.channels[self.channel_one_map].buffer = 0
301-
self.channels[self.channel_one_map].resolution = 10
302-
self.connection.send_byte(CP.ADC)
303-
304-
CH123SA = 0 # TODO what is this?
305-
chosa = self.channels[self.channel_one_map].chosa
306-
if channels == 1:
307-
if self.trigger_enabled:
308-
self.channels[self.channel_one_map].resolution = 12
309-
# Rescale trigger voltage for 12-bit resolution.
310-
self.configure_trigger(
311-
self.channels[self.channel_one_map], self.trigger_voltage
312-
)
313-
self.connection.send_byte(CP.CAPTURE_12BIT)
314-
self.connection.send_byte(chosa | 0x80) # Trigger
315-
elif timegap >= 1:
316-
self.channels[self.channel_one_map].resolution = 12
317-
self.connection.send_byte(CP.CAPTURE_DMASPEED)
318-
self.connection.send_byte(chosa | 0x80) # 12-bit mode
319-
else:
320-
self.connection.send_byte(CP.CAPTURE_DMASPEED)
321-
self.connection.send_byte(chosa) # 10-bit mode
322-
elif channels == 2:
323-
self.channels["CH2"].resolution = 10
324-
self.channels["CH2"].buffer = 1
325-
self.connection.send_byte(CP.CAPTURE_TWO)
326-
self.connection.send_byte(chosa | (0x80 * self.trigger_enabled))
327-
else:
328-
for e, c in enumerate(self.CH234):
329-
self.channels[c].resolution = 10
330-
self.channels[c].buffer = e + 1
331-
self.connection.send_byte(CP.CAPTURE_FOUR)
332-
self.connection.send_byte(
333-
chosa | (CH123SA << 4) | (0x80 * self.trigger_enabled)
334-
)
335-
336-
self.connection.send_int(samples)
337-
self.connection.send_int(int(timegap * 8)) # 8 MHz clock
338-
self.connection.get_ack()
339-
340-
def fetch_data(self, offset_index, samples) -> np.ndarray:
341-
"""Fetch the requested number of samples from specified buffer index.
342-
343-
The ADC hardware buffer can store up to 10000 samples. During simultaneous
344-
sampling of multiple channels, the location of the stored samples are offset
345-
based on the capturing channel. The first channel (which can be remapped with
346-
the channel_one_map attribute of AnalogAcquisitionHandler instances) has an
347-
offset of 0. The second, third, and fourth channels are offset by one, two, or
348-
three multiplied by the number of requested samples.
349-
350-
Parameters
351-
----------
352-
offset_index : {0, 1, 2, 3}
353-
Zero-indexed capture channel from which to fetch data. Index 0 fetches data
354-
from whichever channel was mapped to channel one during capture. Indices
355-
1-3 fetch data from channels CH2, CH3, and MIC.
356-
samples : int
357-
Fetch this many samples from the buffer.
358-
359-
Example
360-
-------
361-
>>> from PSL import achan
362-
>>> analog_channels = achan.AnalogAcquisitionHandler()
363-
>>> analog_channels.capture_nonblocking(channels=2, samples=1600, timegap=1)
364-
# Get the first 1600 samples in the buffer, i.e. indices 0-1599.
365-
>>> y1 = analog_channels.fetch_data(0, 1600)
366-
# Get another 1600 samples from the buffer, starting from index 1600.
367-
>>> y2 = analog_channels.fetch_data(1, 1600)
368-
369-
Returns
370-
-------
371-
numpy.ndarray
372-
One-dimensional array holding the requested voltages.
373-
"""
374-
data = bytearray()
375-
376-
for i in range(int(np.ceil(samples / self.data_splitting))):
377-
self.connection.send_byte(CP.COMMON)
378-
self.connection.send_byte(CP.RETRIEVE_BUFFER)
379-
offset = offset_index * samples + i * self.data_splitting
380-
self.connection.send_int(offset)
381-
self.connection.send_int(self.data_splitting) # Ints to read
382-
# Reading int by int sometimes causes a communication error.
383-
data += self.connection.interface.read(self.data_splitting * 2)
384-
self.connection.get_ack()
385-
386-
data = [CP.ShortInt.unpack(data[s * 2 : s * 2 + 2])[0] for s in range(samples)]
387-
388-
return np.array(data)
389-
390-
def progress(self) -> Tuple[bool, int]:
391-
"""Return the status of a capture call.
392-
393-
Returns
394-
-------
395-
bool, int
396-
A boolean indicating whether the capture is complete, followed by the
397-
number of samples currently held in the buffer.
398-
"""
399-
self.connection.send_byte(CP.ADC)
400-
self.connection.send_byte(CP.GET_CAPTURE_STATUS)
401-
conversion_done = self.connection.get_byte()
402-
samples = self.connection.get_int()
403-
self.connection.get_ack()
404-
405-
return bool(conversion_done), samples
406-
407-
def configure_trigger(self, channel: str, voltage: float, prescaler: int = 0):
408-
"""Configure trigger parameters for capture routines.
409-
410-
The capture routines will wait until a rising edge of the input signal crosses
411-
the specified level. The trigger will timeout within 8 ms, and capture will
412-
start regardless.
413-
414-
To disable the trigger after configuration, set the trigger_enabled attribute
415-
of the AnalogAcquisitionHandler instance to False.
416-
417-
Parameters
418-
----------
419-
channel : {'CH1', 'CH2', 'CH3', 'MIC', 'CAP', 'SEN', 'AN8'}
420-
The name of the trigger channel.
421-
voltage : float
422-
The trigger voltage in volts.
423-
prescaler : int, optional
424-
The default value is 0.
425-
426-
Examples
427-
--------
428-
>>> from PSL import achan
429-
>>> analog_channels = achan.AnalogAcquisitionHandler()
430-
>>> analog_channels.configure_trigger(channel='CH1', voltage=1.1)
431-
>>> xy = analog_channels.capture(channels=1, samples=800, timegap=2)
432-
>>> diff = abs(xy[1, 0] - 1.1) # Should be small unless a timeout occurred.
433-
"""
434-
self._trigger_channel = channel
435-
436-
if channel == self.channel_one_map:
437-
channel = 0
438-
else:
439-
channel == self.CH234.index(channel) + 1
440-
441-
self.connection.send_byte(CP.ADC)
442-
self.connection.send_byte(CP.CONFIGURE_TRIGGER)
443-
# Trigger channel (4lsb) , trigger timeout prescaler (4msb)
444-
self.connection.send_byte((prescaler << 4) | (1 << channel)) # TODO prescaler?
445-
level = self.channels[self._trigger_channel].unscale(voltage)
446-
self.connection.send_int(level)
447-
self.connection.get_ack()
448-
449-
def select_range(self, channel: str, voltage_range: Union[int, float]):
450-
"""Set appropriate gain automatically.
451-
452-
Setting the right voltage range will result in better resolution. In case the
453-
range specified is 160, an external 10 MΩ resistor must be connected in series
454-
with the device.
455-
456-
Parameters
457-
----------
458-
channel : {'CH1', 'CH2'}
459-
Channel on which to apply gain.
460-
voltage_range : {16,8,4,3,2,1.5,1,.5,160}
461-
462-
Examples
463-
--------
464-
>>> from PSL import achan´
465-
>>> analog_channels = achan.AnalogAcquisitionHandler()
466-
>>> analog_channels.select_range('CH1', 8)
467-
# Gain set to 2x on CH1. Voltage range ±8 V.
468-
"""
469-
ranges = [16, 8, 4, 3, 2, 1.5, 1, 0.5, 160]
470-
if voltage_range in ranges:
471-
idx = ranges.index(voltage_range)
472-
gain = GAIN_VALUES[idx]
473-
self.channels[channel] = gain
474-
else:
475-
e = f"Invalid range: {voltage_range}. Valid ranges are {ranges}."
476-
raise ValueError(e)

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy