pico_driver/
ps4000a.rs

1use crate::{
2    dependencies::{load_dependencies, LoadedDependencies},
3    get_version_string, parse_enum_result,
4    trampoline::split_closure,
5    EnumerationResult, PicoDriver,
6};
7use c_vec::CVec;
8use lazy_static::lazy_static;
9use parking_lot::{Mutex, RwLock};
10use pico_common::{
11    ChannelConfig, DownsampleMode, Driver, FromPicoStr, PicoChannel, PicoError, PicoInfo,
12    PicoRange, PicoResult, PicoStatus, SampleConfig, ToPicoStr,
13};
14use pico_sys_dynamic::ps4000a::{PS4000ALoader, PS4000A_USER_PROBE_INTERACTIONS};
15use std::{collections::HashMap, matches, pin::Pin, sync::Arc};
16
17type ChannelRangesMap = HashMap<PicoChannel, Vec<PicoRange>>;
18
19lazy_static! {
20    /// Because the probe callback does not support passing context, we have to
21    /// store the returned probes globally
22    static ref PROBES_4000A: Mutex<HashMap<i16, ChannelRangesMap>> = Default::default();
23}
24
25#[tracing::instrument(level = "debug")]
26extern "C" fn probes_callback_4000a(
27    handle: i16,
28    _status: u32,
29    probes_ptr: *mut PS4000A_USER_PROBE_INTERACTIONS,
30    probes_num: u32,
31) {
32    let probes = unsafe { CVec::new(probes_ptr, probes_num as usize) };
33    let mut probes_4000a = PROBES_4000A.lock();
34
35    let mut device_probes = probes_4000a.get(&handle).unwrap_or(&HashMap::new()).clone();
36
37    for each in probes.iter() {
38        let ch: PicoChannel = each.channel.into();
39        if each.connected == 1 {
40            if each.rangeFirst_ != each.rangeLast_ {
41                let ranges: Vec<PicoRange> = (each.rangeFirst_..each.rangeLast_)
42                    .map(PicoRange::from)
43                    .collect();
44                device_probes.insert(ch, ranges);
45            }
46        } else {
47            device_probes.remove(&ch);
48        }
49    }
50
51    tracing::trace!(
52        "probes_callback_4000a() {{ handle: {}, probes:{:?} }}",
53        handle,
54        device_probes
55    );
56
57    probes_4000a.insert(handle, device_probes);
58}
59
60pub struct PS4000ADriver {
61    _dependencies: LoadedDependencies,
62    bindings: PS4000ALoader,
63}
64
65impl std::fmt::Debug for PS4000ADriver {
66    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67        f.debug_struct("PS4000ADriver").finish()
68    }
69}
70
71impl PS4000ADriver {
72    pub fn new<P>(path: P) -> Result<Self, ::libloading::Error>
73    where
74        P: AsRef<::std::ffi::OsStr>,
75    {
76        let dependencies = load_dependencies(&path.as_ref());
77        let bindings = unsafe { PS4000ALoader::new(path)? };
78        // Disables the splash screen on Windows
79        unsafe { bindings.ps4000aApplyFix(0x1ced9168, 0x11e6) };
80        Ok(PS4000ADriver {
81            bindings,
82            _dependencies: dependencies,
83        })
84    }
85}
86
87impl PicoDriver for PS4000ADriver {
88    fn get_driver(&self) -> Driver {
89        Driver::PS3000A
90    }
91
92    #[tracing::instrument(level = "trace", skip(self))]
93    fn get_version(&self) -> PicoResult<String> {
94        let raw_version = self.get_unit_info(0, PicoInfo::DRIVER_VERSION)?;
95
96        // On non-Windows platforms, the drivers return extra text before the
97        // version string
98        Ok(get_version_string(&raw_version))
99    }
100
101    #[tracing::instrument(level = "trace", skip(self))]
102    fn get_path(&self) -> PicoResult<Option<String>> {
103        Ok(Some(self.get_unit_info(0, PicoInfo::DRIVER_PATH)?))
104    }
105
106    #[tracing::instrument(level = "trace", skip(self))]
107    fn enumerate_units(&self) -> PicoResult<Vec<EnumerationResult>> {
108        let mut device_count = 0i16;
109        let mut serial_buf = "-v".into_pico_i8_string();
110        serial_buf.extend(vec![0i8; 1000]);
111        let mut serial_buf_len = serial_buf.len() as i16;
112
113        let status = PicoStatus::from(unsafe {
114            self.bindings.ps4000aEnumerateUnits(
115                &mut device_count,
116                serial_buf.as_mut_ptr(),
117                &mut serial_buf_len,
118            )
119        });
120
121        match status {
122            PicoStatus::NOT_FOUND => Ok(Vec::new()),
123            PicoStatus::OK => Ok(parse_enum_result(&serial_buf, serial_buf_len as usize)),
124            x => Err(PicoError::from_status(x, "enumerate_units")),
125        }
126    }
127
128    #[tracing::instrument(level = "trace", skip(self))]
129    fn open_unit(&self, serial: Option<&str>) -> PicoResult<i16> {
130        let serial = serial.map(|s| s.into_pico_i8_string());
131
132        let mut handle = -1i16;
133        let mut status = PicoStatus::from(unsafe {
134            match serial {
135                Some(mut serial) => self
136                    .bindings
137                    .ps4000aOpenUnit(&mut handle, serial.as_mut_ptr()),
138                None => self
139                    .bindings
140                    .ps4000aOpenUnit(&mut handle, std::ptr::null_mut()),
141            }
142        });
143
144        // Handle changing power source...
145        if matches!(
146            status,
147            PicoStatus::POWER_SUPPLY_NOT_CONNECTED | PicoStatus::USB3_0_DEVICE_NON_USB3_0_PORT
148        ) {
149            status = PicoStatus::from(unsafe {
150                self.bindings
151                    .ps4000aChangePowerSource(handle, status.into())
152            })
153        }
154
155        match status {
156            PicoStatus::OK => {
157                unsafe {
158                    self.bindings
159                        .ps4000aSetProbeInteractionCallback(handle, Some(probes_callback_4000a));
160                }
161
162                // There is no way to know how many probes we need to wait for
163                std::thread::sleep(std::time::Duration::from_millis(800));
164
165                Ok(handle)
166            }
167            x => Err(PicoError::from_status(x, "open_unit")),
168        }
169    }
170
171    #[tracing::instrument(level = "trace", skip(self))]
172    fn ping_unit(&self, handle: i16) -> PicoResult<()> {
173        PicoStatus::from(unsafe { self.bindings.ps4000aPingUnit(handle) })
174            .to_result((), "ping_unit")
175    }
176
177    #[tracing::instrument(level = "trace", skip(self))]
178    fn maximum_value(&self, handle: i16) -> PicoResult<i16> {
179        let mut value = -1i16;
180
181        PicoStatus::from(unsafe { self.bindings.ps4000aMaximumValue(handle, &mut value) })
182            .to_result(value, "maximum_value")
183    }
184
185    #[tracing::instrument(level = "trace", skip(self))]
186    fn close(&self, handle: i16) -> PicoResult<()> {
187        // Remove probes for 4000a devices when they are closed
188        PROBES_4000A.lock().remove(&handle);
189
190        PicoStatus::from(unsafe { self.bindings.ps4000aCloseUnit(handle) })
191            .to_result((), "close_unit")
192    }
193
194    #[tracing::instrument(level = "trace", skip(self))]
195    fn get_unit_info(&self, handle: i16, info_type: PicoInfo) -> PicoResult<String> {
196        let mut string_buf: Vec<i8> = vec![0i8; 256];
197        let mut string_buf_out_len = 0i16;
198
199        let status = PicoStatus::from(unsafe {
200            self.bindings.ps4000aGetUnitInfo(
201                handle,
202                string_buf.as_mut_ptr(),
203                string_buf.len() as i16,
204                &mut string_buf_out_len,
205                info_type.into(),
206            )
207        });
208
209        match status {
210            PicoStatus::OK => Ok(string_buf.from_pico_i8_string(string_buf_out_len as usize)),
211            x => Err(PicoError::from_status(x, "get_unit_info")),
212        }
213    }
214
215    #[tracing::instrument(level = "trace", skip(self))]
216    fn get_channel_ranges(&self, handle: i16, channel: PicoChannel) -> PicoResult<Vec<PicoRange>> {
217        // Check if we've had probes returned for this device
218        if let Some(probes) = PROBES_4000A.lock().get(&handle) {
219            if let Some(ranges) = probes.get(&channel.clone()) {
220                return Ok(ranges.clone());
221            };
222        }
223
224        let mut ranges = vec![0i32; 30];
225        let mut len = 30i32;
226
227        let status = PicoStatus::from(unsafe {
228            self.bindings.ps4000aGetChannelInformation(
229                handle,
230                0,
231                0,
232                ranges.as_mut_ptr(),
233                &mut len,
234                channel.into(),
235            )
236        });
237
238        match status {
239            PicoStatus::OK => Ok(ranges[0..len as usize]
240                .to_vec()
241                .iter()
242                .map(|v| PicoRange::from(*v))
243                .collect()),
244            x => Err(PicoError::from_status(x, "get_channel_ranges")),
245        }
246    }
247
248    #[tracing::instrument(level = "trace", skip(self))]
249    fn enable_channel(
250        &self,
251        handle: i16,
252        channel: PicoChannel,
253        config: &ChannelConfig,
254    ) -> PicoResult<()> {
255        PicoStatus::from(unsafe {
256            self.bindings.ps4000aSetChannel(
257                handle,
258                channel.into(),
259                1,
260                config.coupling.into(),
261                config.range.into(),
262                config.offset as f32,
263            )
264        })
265        .to_result((), "set_channel")
266    }
267
268    #[tracing::instrument(level = "trace", skip(self))]
269    fn disable_channel(&self, handle: i16, channel: PicoChannel) -> PicoResult<()> {
270        PicoStatus::from(unsafe {
271            self.bindings
272                .ps4000aSetChannel(handle, channel.into(), 0, 0, 0, 0.0)
273        })
274        .to_result((), "set_channel")
275    }
276
277    #[tracing::instrument(level = "trace", skip(self, buffer))]
278    fn set_data_buffer(
279        &self,
280        handle: i16,
281        channel: PicoChannel,
282        buffer: Arc<RwLock<Pin<Vec<i16>>>>,
283        buffer_len: usize,
284    ) -> PicoResult<()> {
285        let mut buffer = buffer.write();
286
287        PicoStatus::from(unsafe {
288            self.bindings.ps4000aSetDataBuffer(
289                handle,
290                channel.into(),
291                buffer.as_mut_ptr(),
292                buffer_len as i32,
293                0,
294                DownsampleMode::NONE.into(),
295            )
296        })
297        .to_result((), "set_data_buffer")
298    }
299
300    #[tracing::instrument(level = "trace", skip(self))]
301    fn start_streaming(
302        &self,
303        handle: i16,
304        sample_config: &SampleConfig,
305    ) -> PicoResult<SampleConfig> {
306        let mut sample_interval = sample_config.interval;
307
308        PicoStatus::from(unsafe {
309            self.bindings.ps4000aRunStreaming(
310                handle,
311                &mut sample_interval,
312                sample_config.units.into(),
313                0,
314                0,
315                (false).into(),
316                1,
317                DownsampleMode::NONE.into(),
318                sample_config.samples_per_second(),
319            )
320        })
321        .to_result(
322            sample_config.with_interval(sample_interval),
323            "start_streaming",
324        )
325    }
326
327    #[tracing::instrument(level = "trace", skip(self, callback))]
328    fn get_latest_streaming_values<'a>(
329        &self,
330        handle: i16,
331        _channels: &[PicoChannel],
332        mut callback: Box<dyn FnMut(usize, usize) + 'a>,
333    ) -> PicoResult<()> {
334        let mut simplify_args =
335            |_: i16, sample_count: i32, start_index: u32, _: i16, _: u32, _: i16, _: i16| {
336                callback(start_index as usize, sample_count as usize);
337            };
338
339        let status = PicoStatus::from(unsafe {
340            let (state, callback) = split_closure(&mut simplify_args);
341
342            self.bindings
343                .ps4000aGetStreamingLatestValues(handle, Some(callback), state)
344        });
345
346        match status {
347            PicoStatus::OK | PicoStatus::BUSY => Ok(()),
348            x => Err(PicoError::from_status(x, "get_latest_streaming_values")),
349        }
350    }
351
352    #[tracing::instrument(level = "trace", skip(self))]
353    fn stop(&self, handle: i16) -> PicoResult<()> {
354        PicoStatus::from(unsafe { self.bindings.ps4000aStop(handle) }).to_result((), "stop")
355    }
356}