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}