Skip to main content

pico_driver/
ps2000.rs

1use crate::{
2    dependencies::{load_dependencies, LoadedDependencies},
3    get_version_string, EnumerationResult, PicoDriver,
4};
5use lazy_static::lazy_static;
6use parking_lot::{Mutex, RwLock};
7use pico_common::{
8    ChannelConfig, Driver, FromPicoStr, PicoChannel, PicoCoupling, PicoError, PicoInfo, PicoRange,
9    PicoResult, PicoStatus, SampleConfig,
10};
11use pico_sys_dynamic::ps2000::PS2000Loader;
12use std::{collections::HashMap, sync::Arc};
13
14type BufferMap = HashMap<PicoChannel, Arc<RwLock<Vec<i16>>>>;
15
16lazy_static! {
17    /// We store buffers so the ps2000 emulates the same API as the other drivers
18    static ref BUFFERS: Mutex<HashMap<i16, BufferMap>> = Default::default();
19}
20
21struct CallbackRef {
22    handle: i16,
23    index: usize,
24}
25
26#[derive(Default)]
27struct LockedCallbackRef {
28    inner: Mutex<Option<CallbackRef>>,
29}
30
31impl LockedCallbackRef {
32    fn start(&self, handle: i16) {
33        loop {
34            let mut inner = self.inner.lock();
35
36            // Check if another device is already waiting on a callback and if
37            // so, we yield and check again
38            if inner.is_none() {
39                *inner = Some(CallbackRef { handle, index: 0 });
40                return;
41            } else {
42                std::thread::yield_now();
43            }
44        }
45    }
46
47    fn callback(&self, overview_buffers: *const *const usize, n_values: usize) {
48        let mut inner = self.inner.lock();
49
50        if let Some(mut callback_ref) = inner.take() {
51            let buffer_pointers =
52                unsafe { std::slice::from_raw_parts::<*const usize>(overview_buffers, 4) };
53
54            let mut all_buffers = BUFFERS.lock();
55            let buffers = all_buffers
56                .get_mut(&callback_ref.handle)
57                .expect("Could not find buffers for this device");
58
59            let mut copy_data = |index: usize, ch: PicoChannel| {
60                let raw_data = unsafe {
61                    std::slice::from_raw_parts::<i16>(
62                        buffer_pointers[index] as *const i16,
63                        n_values,
64                    )
65                };
66                // fetch the buffer to copy the data into it
67                let mut ch_buf = buffers
68                    .get_mut(&ch)
69                    .expect("Could not find buffers for this channel")
70                    .write();
71
72                ch_buf[callback_ref.index..callback_ref.index + n_values].copy_from_slice(raw_data);
73            };
74
75            // ps2000 devices always have two channels so we just handle them manually
76            if !buffer_pointers[0].is_null() {
77                copy_data(0, PicoChannel::A)
78            }
79
80            if !buffer_pointers[2].is_null() {
81                copy_data(2, PicoChannel::B)
82            }
83
84            callback_ref.index += n_values;
85            *inner = Some(callback_ref);
86        } else {
87            panic!("Streaming callback was called without a device reference");
88        }
89    }
90
91    fn end(&self) -> Option<usize> {
92        let mut inner = self.inner.lock();
93        inner.take().map(|cb| cb.index)
94    }
95}
96
97lazy_static! {
98    // The callbacks passed to the ps2000 driver don't support passing context
99    // which is an issue if you want to stream from more than one device at the
100    // same time.
101    //
102    // However, the callback passed to ps2000_get_streaming_last_values is
103    // called before the function returns and we can rely on this to track which
104    // device the callback refers to.
105    static ref CALLBACK_REF: LockedCallbackRef = Default::default();
106}
107
108extern "C" fn streaming_callback(
109    overview_buffers: *mut *mut i16,
110    _overflow: i16,
111    _triggered_at: u32,
112    _triggered: i16,
113    _auto_stop: i16,
114    n_values: u32,
115) {
116    CALLBACK_REF.callback(overview_buffers as *const *const usize, n_values as usize);
117}
118
119pub struct PS2000Driver {
120    _dependencies: LoadedDependencies,
121    bindings: PS2000Loader,
122}
123
124impl std::fmt::Debug for PS2000Driver {
125    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126        f.debug_struct("PS2000Driver").finish()
127    }
128}
129
130impl PS2000Driver {
131    pub fn new<P>(path: P) -> Result<Self, ::libloading::Error>
132    where
133        P: AsRef<::std::ffi::OsStr>,
134    {
135        let dependencies = load_dependencies(path.as_ref());
136        let bindings = unsafe { PS2000Loader::new(path)? };
137        unsafe { bindings.ps2000_apply_fix(0x1ced9168, 0x11e6) };
138        Ok(PS2000Driver {
139            bindings,
140            _dependencies: dependencies,
141        })
142    }
143
144    fn open_unit_base(&self) -> Result<i16, PicoStatus> {
145        match unsafe { self.bindings.ps2000_open_unit() } {
146            -1 => Err(PicoStatus::OPERATION_FAILED),
147            0 => Err(PicoStatus::NOT_FOUND),
148            handle => Ok(handle),
149        }
150    }
151}
152
153impl PicoDriver for PS2000Driver {
154    fn get_driver(&self) -> Driver {
155        Driver::PS2000
156    }
157
158    #[tracing::instrument(level = "trace", skip(self))]
159    fn get_version(&self) -> PicoResult<String> {
160        let raw_version = self.get_unit_info(0, PicoInfo::DRIVER_VERSION)?;
161
162        // On non-Windows platforms, the drivers return extra text before the
163        // version string
164        Ok(get_version_string(&raw_version))
165    }
166
167    #[tracing::instrument(level = "trace", skip(self))]
168    fn get_path(&self) -> PicoResult<Option<String>> {
169        Ok(None)
170    }
171
172    // The ps2000 driver does not support proper enumeration like the other
173    // drivers. We emulate enumeration by opening all the available devices
174    // and getting their serial numbers.
175    #[tracing::instrument(level = "trace", skip(self))]
176    fn enumerate_units(&self) -> PicoResult<Vec<EnumerationResult>> {
177        let mut output = Vec::new();
178        // We keep track of handles to close when we're finished
179        let mut handles_to_close = Vec::new();
180
181        loop {
182            match self.open_unit_base() {
183                Ok(handle) => {
184                    handles_to_close.push(handle);
185
186                    let serial = self.get_unit_info(handle, PicoInfo::BATCH_AND_SERIAL)?;
187                    let variant = self.get_unit_info(handle, PicoInfo::VARIANT_INFO)?;
188                    output.push(EnumerationResult { variant, serial });
189                }
190                Err(PicoStatus::NOT_FOUND) => break,
191                Err(e) => {
192                    for each in handles_to_close {
193                        let _ = self.close(each);
194                    }
195
196                    return Err(PicoError::from_status(e, "open_unit"));
197                }
198            }
199        }
200
201        for each in handles_to_close {
202            let _ = self.close(each);
203        }
204
205        Ok(output)
206    }
207
208    // The ps2000 driver cannot open devices by serial number like the other
209    // drivers. We emulate the other driver behaviour by opening devices until
210    // we find the correct one.
211    #[tracing::instrument(level = "trace", skip(self))]
212    fn open_unit(&self, serial: Option<&str>) -> PicoResult<i16> {
213        // We keep track of handles to close when we're finished
214        let mut handles_to_close = Vec::new();
215
216        loop {
217            match self.open_unit_base() {
218                Ok(handle) => {
219                    if let Some(serial) = serial {
220                        if serial == self.get_unit_info(handle, PicoInfo::BATCH_AND_SERIAL)? {
221                            for each in handles_to_close {
222                                let _ = self.close(each);
223                            }
224
225                            return Ok(handle);
226                        } else {
227                            handles_to_close.push(handle);
228                        }
229                    } else {
230                        return Ok(handle);
231                    }
232                }
233                Err(e) => {
234                    for each in handles_to_close {
235                        let _ = self.close(each);
236                    }
237
238                    return Err(PicoError::from_status(e, "open_unit"));
239                }
240            }
241        }
242    }
243
244    #[tracing::instrument(level = "trace", skip(self))]
245    fn ping_unit(&self, handle: i16) -> PicoResult<()> {
246        PicoStatus::from(unsafe { self.bindings.ps2000PingUnit(handle) }).to_result((), "ping_unit")
247    }
248
249    #[tracing::instrument(level = "trace", skip(self))]
250    fn maximum_value(&self, _: i16) -> PicoResult<i16> {
251        // The ps2000 driver cannot be queried for max adc value, but it's a constant
252        Ok(32_767)
253    }
254
255    #[tracing::instrument(level = "trace", skip(self))]
256    fn close(&self, handle: i16) -> PicoResult<()> {
257        // Remove any buffers which have been allocated for this device
258        let mut buffers = BUFFERS.lock();
259        buffers.remove(&handle);
260
261        PicoStatus::from(unsafe { self.bindings.ps2000_close_unit(handle) })
262            .to_result((), "close_unit")
263    }
264
265    #[tracing::instrument(level = "trace", skip(self))]
266    fn get_unit_info(&self, handle: i16, info_type: PicoInfo) -> PicoResult<String> {
267        let mut string_buf: Vec<i8> = vec![0i8; 256];
268
269        let status = PicoStatus::from(unsafe {
270            self.bindings.ps2000_get_unit_info(
271                handle,
272                string_buf.as_mut_ptr(),
273                string_buf.len() as i16,
274                info_type.into(),
275            )
276        });
277
278        match status {
279            PicoStatus::OK => Ok(string_buf.from_pico_i8_string(255)),
280            x => Err(PicoError::from_status(x, "get_unit_info")),
281        }
282    }
283
284    #[tracing::instrument(level = "trace", skip(self))]
285    fn get_channel_ranges(&self, handle: i16, channel: PicoChannel) -> PicoResult<Vec<PicoRange>> {
286        // There is no way to query the ps2000 driver for valid input ranges for
287        // each variant. However we can attempt to set all the ranges and only
288        // return those that succeed!
289        Ok((1..=10)
290            .flat_map(|r| -> PicoResult<PicoRange> {
291                let range = PicoRange::from(r);
292                let config = ChannelConfig {
293                    coupling: PicoCoupling::DC,
294                    range,
295                    offset: 0.0,
296                };
297
298                self.enable_channel(handle, channel, &config)?;
299                Ok(range)
300            })
301            .collect())
302    }
303
304    #[tracing::instrument(level = "trace", skip(self))]
305    fn enable_channel(
306        &self,
307        handle: i16,
308        channel: PicoChannel,
309        config: &ChannelConfig,
310    ) -> PicoResult<()> {
311        PicoStatus::from(unsafe {
312            self.bindings.ps2000_set_channel(
313                handle,
314                channel.into(),
315                1,
316                config.coupling.into(),
317                config.range.into(),
318            )
319        })
320        .to_result((), "set_channel")
321    }
322
323    fn disable_channel(&self, handle: i16, channel: PicoChannel) -> PicoResult<()> {
324        PicoStatus::from(unsafe {
325            self.bindings
326                .ps2000_set_channel(handle, channel.into(), 0, 0, 0)
327        })
328        .to_result((), "set_channel")
329    }
330
331    // The ps2000 driver doesn't copy data into supplied buffers. It passes the
332    // buffers in the callback. Here we store the buffers and try and emulate
333    // the other drivers
334    #[tracing::instrument(level = "trace", skip(self, buffer))]
335    fn set_data_buffer(
336        &self,
337        handle: i16,
338        channel: PicoChannel,
339        buffer: Arc<RwLock<Vec<i16>>>,
340        _buffer_len: usize,
341    ) -> PicoResult<()> {
342        let mut buffers = BUFFERS.lock();
343
344        buffers
345            .entry(handle)
346            .and_modify(|e| {
347                e.insert(channel, buffer.clone());
348            })
349            .or_insert_with(|| {
350                let mut hashmap = HashMap::new();
351                hashmap.insert(channel, buffer);
352                hashmap
353            });
354
355        Ok(())
356    }
357
358    #[tracing::instrument(level = "trace", skip(self))]
359    fn start_streaming(
360        &self,
361        handle: i16,
362        sample_config: &SampleConfig,
363        _enabled_channels: u8,
364    ) -> PicoResult<SampleConfig> {
365        let status = PicoStatus::from(unsafe {
366            self.bindings.ps2000_run_streaming_ns(
367                handle,
368                sample_config.interval,
369                sample_config.units.into(),
370                sample_config.samples_per_second(),
371                (false).into(),
372                1,
373                1_000_000,
374            )
375        });
376
377        // TODO: correctly handle error codes from status
378        // if status != PicoStatus::OK {
379        //     self.get_unit_info(handle, PicoInfo::KERNEL_VERSION)?;
380        // }
381
382        status.to_result(*sample_config, "start_streaming")
383    }
384
385    #[tracing::instrument(level = "trace", skip(self, callback))]
386    fn get_latest_streaming_values<'a>(
387        &self,
388        handle: i16,
389        _channels: &[PicoChannel],
390        mut callback: Box<dyn FnMut(usize, usize) + 'a>,
391    ) -> PicoResult<()> {
392        CALLBACK_REF.start(handle);
393
394        unsafe {
395            self.bindings
396                .ps2000_get_streaming_last_values(handle, Some(streaming_callback))
397        };
398
399        if let Some(sample_count) = CALLBACK_REF.end() {
400            callback(0, sample_count);
401        }
402
403        Ok(())
404    }
405
406    #[tracing::instrument(level = "trace", skip(self))]
407    fn stop(&self, handle: i16) -> PicoResult<()> {
408        PicoStatus::from(unsafe { self.bindings.ps2000_stop(handle) }).to_result((), "stop")
409    }
410}
411
412impl Drop for PS2000Driver {
413    #[tracing::instrument(level = "trace", skip(self))]
414    fn drop(&mut self) {}
415}