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