|
1 |
| -import time |
2 |
| -from typing import List, Tuple, Union |
| 1 | +from typing import List, Union |
3 | 2 |
|
4 | 3 | import numpy as np
|
5 | 4 |
|
6 |
| -from PSL.analyticsClass import analyticsClass |
7 | 5 | import PSL.commands_proto as CP
|
8 | 6 | from PSL import packet_handler
|
9 | 7 |
|
@@ -172,305 +170,3 @@ def unscale(self, voltage: float) -> int:
|
172 | 170 | level = np.clip(level, 0, self._resolution)
|
173 | 171 | level = np.round(level)
|
174 | 172 | 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