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 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 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 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 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 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 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 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}