Skip to main content

pico_driver/
lib.rs

1//! Common, safe wrappers for Pico Technology oscilloscope drivers.
2//!
3//! This is a sub crate that you probably don't want to use directly. Try the top level
4//! [`pico-sdk`](https://crates.io/crates/pico-sdk) crate which exposes everything from here.
5//!
6//! Each Pico Technology oscilloscope relies on a native driver for communication and this driver will
7//! vary depending on the device product range. Each of these drivers has an interface which differs by
8//! either a few function arguments or a vastly differing API.
9//!
10//! `PS2000Driver`, `PS2000ADriver`, `PS3000ADriver`, `PS4000Driver`,
11//! `PS4000ADriver`, `PS5000ADriver`, `PS6000Driver` and `PS6000ADriver` wrap
12//! their corresponding loaders and expose a safe, common API by implementing
13//! the `PicoDriver` trait. These can be constructed with a `Resolution` which tells the wrapper where
14//! to resolve the dynamic library from.
15//!
16//! # Examples
17//! Using the raw safe bindings to open and configure the first available device:
18//! ```no_run
19//! # fn run() -> Result<(),Box<dyn std::error::Error>> {
20//! use pico_common::{ChannelConfig, Driver, PicoChannel, PicoCoupling, PicoInfo, PicoRange};
21//! use pico_driver::LibraryResolution;
22//!
23//! // Load the ps2000 driver library with the default resolution
24//! let driver = LibraryResolution::Default.try_load(Driver::PS2000)?;
25//! // Load the ps4000a driver library from the applications root directory
26//! let driver = LibraryResolution::AppRoot.try_load(Driver::PS4000A)?;
27//!
28//! // Open the first device
29//! let handle = driver.open_unit(None)?;
30//! let variant = driver.get_unit_info(handle, PicoInfo::VARIANT_INFO)?;
31//!
32//! let ch_config = ChannelConfig {
33//!     coupling: PicoCoupling::DC,
34//!     range: PicoRange::X1_PROBE_2V,
35//!     offset: 0.0
36//! };
37//!
38//! driver.enable_channel(handle, PicoChannel::A, &ch_config)?;
39//! # Ok(())
40//! # }
41//! ```
42
43#![allow(clippy::upper_case_acronyms)]
44
45use parking_lot::RwLock;
46use pico_common::{
47    ChannelConfig, Driver, FromPicoStr, PicoChannel, PicoError, PicoInfo, PicoRange, PicoResult,
48    SampleConfig,
49};
50pub use resolution::LibraryResolution;
51use std::{fmt, sync::Arc};
52use thiserror::Error;
53use version_compare::Version;
54
55mod dependencies;
56pub mod kernel_driver;
57pub mod ps2000;
58pub mod ps2000a;
59pub mod ps3000a;
60pub mod ps4000;
61pub mod ps4000a;
62pub mod ps5000a;
63pub mod ps6000;
64pub mod ps6000a;
65pub mod psospa;
66mod resolution;
67mod trampoline;
68
69/// Covers the various errors encountered when attempting to load drivers
70#[derive(Error, Debug)]
71pub enum DriverLoadError {
72    #[error("Pico driver error: {0}")]
73    DriverError(#[from] PicoError),
74
75    #[error("Library load error: {0}")]
76    LibloadingError(#[from] libloading::Error),
77
78    #[error("Invalid Driver Version: Requires >= {required}, Found: {found}")]
79    VersionError { found: String, required: String },
80}
81
82/// Serial and variant pairs returned by driver enumeration
83#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
84#[derive(Clone, Debug)]
85pub struct EnumerationResult {
86    pub variant: String,
87    pub serial: String,
88}
89
90/// Common trait implemented for every driver
91pub trait PicoDriver: fmt::Debug + Send + Sync {
92    /// Gets the underlying driver type
93    fn get_driver(&self) -> Driver;
94    /// Gets the driver version string
95    fn get_version(&self) -> PicoResult<String>;
96    /// Gets the path to the loaded driver
97    fn get_path(&self) -> PicoResult<Option<String>>;
98    /// Returns a list of discovered serial numbers
99    fn enumerate_units(&self) -> PicoResult<Vec<EnumerationResult>>;
100    /// Opens a device, optionally with a specific serial number
101    fn open_unit(&self, serial: Option<&str>) -> PicoResult<i16>;
102    /// Ping a unit to see if it's still connected
103    fn ping_unit(&self, handle: i16) -> PicoResult<()>;
104    /// Get the maximum expected ADC value. This is required to scale to volts
105    fn maximum_value(&self, handle: i16) -> PicoResult<i16>;
106    /// Close the specified unit
107    fn close(&self, handle: i16) -> PicoResult<()>;
108    /// Get one of the unit info strings
109    fn get_unit_info(&self, handle: i16, info_type: PicoInfo) -> PicoResult<String>;
110    /// Get valid ranges for the specified channel
111    fn get_channel_ranges(&self, handle: i16, channel: PicoChannel) -> PicoResult<Vec<PicoRange>>;
112    /// Set up a channel with the supplied config
113    fn enable_channel(
114        &self,
115        handle: i16,
116        channel: PicoChannel,
117        config: &ChannelConfig,
118    ) -> PicoResult<()>;
119    /// Disable a channel
120    fn disable_channel(&self, handle: i16, channel: PicoChannel) -> PicoResult<()>;
121    /// Give the driver a buffer to write data into
122    fn set_data_buffer(
123        &self,
124        handle: i16,
125        channel: PicoChannel,
126        buffer: Arc<RwLock<Vec<i16>>>,
127        buffer_len: usize,
128    ) -> PicoResult<()>;
129    /// Starts the device streaming
130    fn start_streaming(
131        &self,
132        handle: i16,
133        sample_config: &SampleConfig,
134        enabled_channels: u8,
135    ) -> PicoResult<SampleConfig>;
136    /// Gets the latest streaming values
137    fn get_latest_streaming_values<'a>(
138        &self,
139        handle: i16,
140        channels: &[PicoChannel],
141        callback: Box<dyn FnMut(usize, usize) + 'a>,
142    ) -> PicoResult<()>;
143    /// Stops the device streaming
144    fn stop(&self, handle: i16) -> PicoResult<()>;
145    /// Check that the driver meets the minimum version tested with these wrappers
146    fn check_version(&self) -> Result<(), DriverLoadError> {
147        let loaded_str = &self.get_version()?;
148        let loaded_version = Version::from(loaded_str);
149
150        let required_str = get_min_required_version(self.get_driver());
151        let required_version = Version::from(required_str);
152
153        if loaded_version < required_version {
154            Err(DriverLoadError::VersionError {
155                found: loaded_str.to_string(),
156                required: required_str.to_string(),
157            })
158        } else {
159            Ok(())
160        }
161    }
162}
163
164pub type ArcDriver = Arc<dyn PicoDriver>;
165
166pub(crate) fn get_version_string(input: &str) -> String {
167    input
168        .split(|s| s == ' ' || s == ',')
169        .last()
170        .expect("Invalid version string")
171        .to_string()
172}
173
174pub(crate) fn parse_enum_result(buffer: &[i8], len: usize) -> Vec<EnumerationResult> {
175    let serials_list = buffer.from_pico_i8_string(len);
176
177    serials_list
178        .split(',')
179        .map(String::from)
180        .map(|device| {
181            let parts = device.split('[').collect::<Vec<_>>();
182
183            EnumerationResult {
184                serial: parts[0].to_string(),
185                variant: parts[1].trim_end_matches(']').to_string(),
186            }
187        })
188        .collect()
189}
190
191/// Gets the minimum supported driver version
192fn get_min_required_version(driver: Driver) -> &'static str {
193    match driver {
194        Driver::PS2000 => "3.0.30.1878",
195        Driver::PS2000A
196        | Driver::PS3000A
197        | Driver::PS4000
198        | Driver::PS4000A
199        | Driver::PS5000A
200        | Driver::PS6000 => "2.1.30.1878",
201        Driver::PS6000A => "1.0.54.2438",
202        _ => panic!(
203            "We don't know the minimum required version for the {:?} driver!",
204            driver,
205        ),
206    }
207}