ic_md/
lib.rs

1//! Driver for the iC-MD quadrature counter.
2//! Built fully in Rust, uses [embedded_hal] and [device_driver].
3//!
4//! <div class="warning">
5//!
6//! **Important Note:**
7//!
8//! This driver is in active development and not yet feature complete. Please see the section below
9//! on Limitations for more details. This driver is currently only available via `git`.
10//!
11//! Any comments are welcome!
12//!
13//! </div>
14//!
15//! # Introduction
16//!
17//! The `IcMd` struct provides a high-level interface to interact with the iC-MD quadrature
18//! counter. However, you can also access the underlying device driver directly via the `device`
19//! field. Please read the device driver documentation for more information on what to expect
20//! when interfacing with the device driver directly.
21//! This low-level access is a temporary solution until the high-level interface is fully
22//! developed. When this well be the case is unclear. If you are interested in it, please let me
23//! know and I'm happy to prioritize the high-level features that are interesting to you.
24//!
25//! # Limitations
26//!
27//! The following features are currently only accessible via the low-level interface:
28//!
29//! - Reference register readout: It is unclear if this currently works, see code comment.
30//!
31//! The following features are currently not yet implemented:
32//!
33//! - Differential or TTL inputs (Address 0x01, bit 7)
34//! - Configuration to have Z signal clear counters 0 and/or 1 (Address 0x01, bits 5 and 6)
35//! - Z signal configuration (Address 0x01, bits 3 and 4)
36//! - Touch probe and AB registers (Address 0x01, bits 1 and 2)
37//! - Differential input configuration selection (RS-422 (default) or LVDS) (Address 0x03, bit 7)
38//!
39//! # Example Usage
40//!
41//! ```rust
42//! # use embedded_hal_mock::eh1::spi::{Mock, Transaction};
43//! # use ic_md::IcMd;
44//! # let expectations = [
45//! #     Transaction::transaction_start(),
46//! #     Transaction::write(0x00),
47//! #     Transaction::write(0x02),
48//! #     Transaction::transaction_end(),
49//! #     Transaction::transaction_start(),
50//! #     Transaction::write(0x80 | 0x08),
51//! #     Transaction::read_vec(vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xC0]),
52//! #     Transaction::transaction_end(),
53//! # ];
54//! // Initialize your SPIDevice, here we are mocking a device!
55//! let mut spi_device = Mock::new(&expectations);
56//!
57//! // Get a handle to the counter with the default setup
58//! let mut icmd = IcMd::new(&mut spi_device);
59//!
60//! // Initialize the counter
61//! icmd.init().unwrap();
62//!
63//! // Read out the counter
64//! let counter_value = icmd.read_counter().unwrap();
65//!
66//! // We can use the get counter methods to access the values. This will return an `Option`
67//! // containing an `i64` value of the count (if the counter is setup, otherwise `None`).
68//! let cnt_0 = counter_value
69//!     .get_cnt0()
70//!     .expect("Counter 0 should always be set up");
71//!
72//! assert_eq!(cnt_0, 42);
73//!
74//! // Last, let us ensure that there are no errors or warnings in the device status. We can use
75//! // the `.is_ok()` method on the `DeviceStatus` struct to do this.
76//! assert!(icmd.get_device_status().is_ok());
77//! #  
78//! # // Check that all our expectations are met - testing only
79//! # spi_device.done();
80//! ```
81//!
82//! # Further help
83//!
84//! For further help and examples, please have a look at the `test` directory in the GitHub
85//! repository, which you can find [here](https://github.com/trappitsch/ic-md/).
86//! There you will find various integration tests that show how to use the driver in practice and
87//! that contain detailed comments on for you.
88
89#![deny(warnings, missing_docs)]
90#![cfg_attr(not(test), no_std)]
91
92use core::{fmt::Debug, result::Result};
93use embedded_hal::spi::SpiDevice;
94
95use dd::{Device, DeviceError, DeviceInterface};
96
97pub use configs::*;
98
99pub mod configs;
100pub mod dd;
101
102/// The main driver struct of the crate representing the iC-MD quadrature counter.
103/// You can also access the underlying device driver directly via the `device` field.
104/// You are then yourself responsible for reading the correct counter configurations.
105#[derive(Debug)]
106pub struct IcMd<Spi> {
107    /// Provides acces to the underlying device driver.
108    pub device: Device<DeviceInterface<Spi>>,
109    /// Configuration of the counter, set only prior to calling `init()`.
110    counter_config: CntCfg,
111    /// Status of the device (error and warning flags). Read only, updated when reading the
112    /// counter.
113    device_status: DeviceStatus,
114    actuator_status: ActuatorStatus,
115}
116
117impl<Spi: SpiDevice> IcMd<Spi> {
118    /// Creates a new instance of the iC-MD driver.
119    /// By default, the counter is configured to 48-bit mode.
120    pub fn new(spi: Spi) -> Self {
121        Self {
122            device: Device::new(DeviceInterface::new(spi)),
123            counter_config: CntCfg::Cnt1Bit48(CntSetup::default()),
124            actuator_status: ActuatorStatus::default(),
125            device_status: DeviceStatus::default(),
126        }
127    }
128
129    /// Initialize the iC-MD device with the given configuration.
130    pub fn init(&mut self) -> Result<(), DeviceError<Spi::Error>> {
131        self.device
132            .counter_configuration()
133            .write(|reg| reg.set_value(self.counter_config.into()))?;
134
135        Ok(())
136    }
137
138    /// Set the actuator pins output to the given status.
139    /// Note that as far as the iC-MD is concerned, this status is "write only". Thus, there is no
140    /// function available to read the current status of the actuator pins. However, the stored
141    /// `actuator_status` variable will be updated according to what you set here.
142    ///
143    /// # Arguments
144    /// * `act0`: The status of actuator pin 0 (ACT0).
145    /// * `act1`: The status of actuator pin 1 (ACT1).
146    pub fn configure_actuator_pins(
147        &mut self,
148        act0: &PinStatus,
149        act1: &PinStatus,
150    ) -> Result<(), DeviceError<Spi::Error>> {
151        self.device.instruction_byte().write(|reg| {
152            reg.set_act_0(act0.into());
153            reg.set_act_1(act1.into());
154        })?;
155        self.actuator_status.act0 = *act0;
156        self.actuator_status.act1 = *act1;
157        Ok(())
158    }
159
160    /// Get current device status.
161    /// This is a cached value that is updated when reading the counter. It contains the error and
162    /// warning flags of the device. For a full device status, use `get_full_device_status()`.
163    pub fn get_device_status(&self) -> DeviceStatus {
164        self.device_status
165    }
166
167    /// Get the full device status by reading all the status registers.
168    /// This will reset many of the status bits to wait for the next event, problem, issue to
169    /// occur.
170    pub fn get_full_device_status(&mut self) -> Result<FullDeviceStatus, DeviceError<Spi::Error>> {
171        let status0 = self.device.status_0().read()?;
172        let status1 = self.device.status_1().read()?;
173        let status2 = self.device.status_2().read()?;
174
175        Ok(FullDeviceStatus {
176            cnt0_overflow: status0.ovf_0().into(),
177            cnt0_aberr: status0.ab_err_0().into(),
178            cnt0_zero: status0.zero_0().into(),
179            cnt1_overflow: status1.ovf_1().into(),
180            cnt1_aberr: status1.ab_err_1().into(),
181            cnt1_zero: status1.zero_1().into(),
182            cnt2_overflow: status2.ovf_2().into(),
183            cnt2_aberr: status2.ab_err_2().into(),
184            cnt2_zero: status2.zero_2().into(),
185            power_status: status0.p_dwn().into(),
186            ref_reg_status: status0.r_val().into(),
187            upd_reg_status: status0.upd_val().into(),
188            ref_cnt_status: status0.ovf_ref().into(),
189            ext_err_status: status1.ext_err().into(),
190            ext_warn_status: status1.ext_warn().into(),
191            comm_status: status1.com_col().into(),
192            tp_status: status0.tp_val().into(),
193            tpi_status: status1.tps().into(),
194            ssi_enabled: status2.en_ssi().into(),
195        })
196    }
197
198    /// Read the current counter value and return it.
199    pub fn read_counter(&mut self) -> Result<CntCount, DeviceError<Spi::Error>> {
200        match self.counter_config {
201            CntCfg::Cnt1Bit24(_) => {
202                let res = self.device.read_cnt_cfg_0().read()?;
203                self.set_device_status(res.nwarn(), res.nerr());
204                Ok(CntCount::Cnt1Bit24(res.cnt_0()))
205            }
206            CntCfg::Cnt2Bit24(_, _) => {
207                let res = self.device.read_cnt_cfg_1().read()?;
208                self.set_device_status(res.nwarn(), res.nerr());
209                Ok(CntCount::Cnt2Bit24(res.cnt_0(), res.cnt_1()))
210            }
211            CntCfg::Cnt1Bit48(_) => {
212                let res = self.device.read_cnt_cfg_2().read()?;
213                self.set_device_status(res.nwarn(), res.nerr());
214                Ok(CntCount::Cnt1Bit48(res.cnt_0()))
215            }
216            CntCfg::Cnt1Bit16(_) => {
217                let res = self.device.read_cnt_cfg_3().read()?;
218                self.set_device_status(res.nwarn(), res.nerr());
219                Ok(CntCount::Cnt1Bit16(res.cnt_0()))
220            }
221            CntCfg::Cnt1Bit32(_) => {
222                let res = self.device.read_cnt_cfg_4().read()?;
223                self.set_device_status(res.nwarn(), res.nerr());
224                Ok(CntCount::Cnt1Bit32(res.cnt_0()))
225            }
226            CntCfg::Cnt2Bit32Bit16(_, _) => {
227                let res = self.device.read_cnt_cfg_5().read()?;
228                self.set_device_status(res.nwarn(), res.nerr());
229                Ok(CntCount::Cnt2Bit32Bit16(res.cnt_0(), res.cnt_1()))
230            }
231            CntCfg::Cnt2Bit16(_, _) => {
232                let res = self.device.read_cnt_cfg_6().read()?;
233                self.set_device_status(res.nwarn(), res.nerr());
234                Ok(CntCount::Cnt2Bit16(res.cnt_0(), res.cnt_1()))
235            }
236            CntCfg::Cnt3Bit16(_, _, _) => {
237                let res = self.device.read_cnt_cfg_7().read()?;
238                self.set_device_status(res.nwarn(), res.nerr());
239                Ok(CntCount::Cnt3Bit16(res.cnt_0(), res.cnt_1(), res.cnt_2()))
240            }
241        }
242    }
243
244    /// Reset counters to zero.
245    /// You can select which counters should be set to zero using the specific arguments.
246    ///
247    /// # Arguments
248    /// * `cnt0`: If true, counter 0 is reset, else not.
249    /// * `cnt1`: If true, counter 1 is reset, else not.
250    /// * `cnt2`: If true, counter 2 is reset, else not.
251    pub fn reset_counters(
252        &mut self,
253        cnt0: bool,
254        cnt1: bool,
255        cnt2: bool,
256    ) -> Result<(), DeviceError<Spi::Error>> {
257        let act0 = &self.actuator_status.act0;
258        let act1 = &self.actuator_status.act1;
259        self.device.instruction_byte().write(|reg| {
260            reg.set_ab_res_0(cnt0);
261            reg.set_ab_res_1(cnt1);
262            reg.set_ab_res_2(cnt2);
263            reg.set_act_0(act0.into());
264            reg.set_act_1(act1.into());
265        })?;
266        Ok(())
267    }
268
269    /// Reset all counters.
270    /// Can be used to send reset commands to all counters.
271    pub fn reset_all_counters(&mut self) -> Result<(), DeviceError<Spi::Error>> {
272        self.reset_counters(true, true, true)?;
273        Ok(())
274    }
275
276    /// Touch probe instruction
277    /// Load touch probe 2 with touch probe 1 value and touch probe 1 wiht ABCNT value.
278    pub fn touch_probe_instruction(&mut self) -> Result<(), DeviceError<Spi::Error>> {
279        let act0 = &self.actuator_status.act0;
280        let act1 = &self.actuator_status.act1;
281        self.device.instruction_byte().write(|reg| {
282            reg.set_tp(true);
283            reg.set_act_0(act0.into());
284            reg.set_act_1(act1.into());
285        })?;
286        Ok(())
287    }
288
289    /// Set the counter configuration.
290    /// This should be done prior to calling `init()`.
291    pub fn set_counter_config(&mut self, config: CntCfg) {
292        self.counter_config = config;
293    }
294
295    /// Set device status from two bools that were read and passed on to here.
296    /// Note taat the inputs are from nerr and nwarn!
297    fn set_device_status(&mut self, nwarn: bool, nerr: bool) {
298        self.device_status.warning = match nwarn {
299            true => WarningStatus::Ok,
300            false => WarningStatus::Warning,
301        };
302        self.device_status.error = match nerr {
303            true => ErrorStatus::Ok,
304            false => ErrorStatus::Error,
305        };
306    }
307}