as5048a_async/
driver.rs

1//! Asynchronous driver for AS5048A magnetic position sensor
2
3use embedded_hal_async::spi::SpiDevice;
4
5use crate::{diagnostics::Diagnostics, error::Error, register::Register, utils};
6
7const READ_BIT: u16 = 0x4000;
8const PARITY_BIT: u16 = 0x8000;
9const ERROR_FLAG: u16 = 0x4000;
10const DATA_MASK: u16 = 0x3FFF;
11const NOP_COMMAND: u16 = 0x0000;
12
13/// Maximum angle value (14-bit: 0-16383, representing 0-360°)
14pub const ANGLE_MAX: u16 = 0x3FFF + 1;
15
16/// AS5048A driver instance (asynchronous)
17#[derive(Debug)]
18#[cfg_attr(feature = "defmt", derive(defmt::Format))]
19pub struct As5048a<SPI> {
20    spi: SPI,
21}
22
23impl<SPI, E> As5048a<SPI>
24where
25    SPI: SpiDevice<u8, Error = E>,
26{
27    /// Create a new AS5048A driver instance
28    pub fn new(spi: SPI) -> Self {
29        Self { spi }
30    }
31
32    /// Release the SPI bus, consuming the driver
33    pub fn release(self) -> SPI {
34        self.spi
35    }
36
37    /// Read a register from the AS5048A
38    ///
39    /// This follows the command-response protocol:
40    /// - Transaction 1: Send read command, ignore response
41    /// - Transaction 2: Send NOP, receive actual data
42    async fn read_register(&mut self, register: Register) -> Result<u16, Error<E>> {
43        let address = u16::from(register);
44
45        let command = READ_BIT | address;
46
47        let command = if utils::calculate_parity(command) {
48            PARITY_BIT | command
49        } else {
50            command
51        };
52
53        #[cfg(feature = "defmt")]
54        defmt::trace!(
55            "Reading register 0x{:04X}, command: 0x{:04X}",
56            address,
57            command
58        );
59
60        let tx_cmd = command.to_be_bytes();
61        let mut rx_cmd = [0u8; 2];
62        self.spi
63            .transfer(&mut rx_cmd, &tx_cmd)
64            .await
65            .map_err(Error::Communication)?;
66
67        let tx_nop = NOP_COMMAND.to_be_bytes();
68        let mut rx_data = [0u8; 2];
69        self.spi
70            .transfer(&mut rx_data, &tx_nop)
71            .await
72            .map_err(Error::Communication)?;
73
74        let response = u16::from_be_bytes(rx_data);
75
76        #[cfg(feature = "defmt")]
77        defmt::trace!("Received response: 0x{:04X}", response);
78
79        if !utils::verify_parity(response) {
80            #[cfg(feature = "defmt")]
81            defmt::warn!("Parity error in response: 0x{:04X}", response);
82            return Err(Error::ParityError);
83        }
84
85        if response & ERROR_FLAG != 0 {
86            #[cfg(feature = "defmt")]
87            defmt::warn!("Sensor error flag set in response");
88            return Err(Error::SensorError);
89        }
90
91        let data = response & DATA_MASK;
92        #[cfg(feature = "defmt")]
93        defmt::debug!("Register 0x{:04X} value: 0x{:04X}", address, data);
94
95        Ok(data)
96    }
97
98    /// Write a register to the AS5048A
99    ///
100    /// This follows the write protocol:
101    /// - Transaction 1: Send write command
102    /// - Transaction 2: Send data frame
103    /// - Transaction 3: Send NOP to verify write
104    ///
105    /// # Errors
106    ///
107    /// Returns an error if:
108    /// - SPI communication fails
109    /// - Parity check fails on the response
110    /// - The sensor reports an error
111    #[allow(dead_code)]
112    async fn write_register(&mut self, register: Register, data: u16) -> Result<(), Error<E>> {
113        let address = u16::from(register);
114
115        #[cfg(feature = "defmt")]
116        defmt::debug!("Writing 0x{:04X} to register 0x{:04X}", data, address);
117
118        let command = address;
119
120        let command = if utils::calculate_parity(command) {
121            PARITY_BIT | command
122        } else {
123            command
124        };
125
126        let tx_cmd = command.to_be_bytes();
127        let mut rx_cmd = [0u8; 2];
128        self.spi
129            .transfer(&mut rx_cmd, &tx_cmd)
130            .await
131            .map_err(Error::Communication)?;
132
133        let data_frame = data & DATA_MASK;
134        let data_frame = if utils::calculate_parity(data_frame) {
135            PARITY_BIT | data_frame
136        } else {
137            data_frame
138        };
139
140        let tx_data = data_frame.to_be_bytes();
141        let mut rx_old = [0u8; 2];
142        self.spi
143            .transfer(&mut rx_old, &tx_data)
144            .await
145            .map_err(Error::Communication)?;
146
147        let tx_nop = NOP_COMMAND.to_be_bytes();
148        let mut rx_verify = [0u8; 2];
149        self.spi
150            .transfer(&mut rx_verify, &tx_nop)
151            .await
152            .map_err(Error::Communication)?;
153
154        let response = u16::from_be_bytes(rx_verify);
155
156        if !utils::verify_parity(response) {
157            #[cfg(feature = "defmt")]
158            defmt::warn!("Parity error in write verification: 0x{:04X}", response);
159            return Err(Error::ParityError);
160        }
161
162        if response & ERROR_FLAG != 0 {
163            #[cfg(feature = "defmt")]
164            defmt::warn!("Sensor error flag set during write");
165            return Err(Error::SensorError);
166        }
167
168        #[cfg(feature = "defmt")]
169        defmt::trace!("Write to register 0x{:04X} successful", address);
170
171        Ok(())
172    }
173
174    /// Get the 14-bit corrected angular position
175    ///
176    /// Value ranges from 0 to 16383 (0° to 359.978°)
177    /// Use [`ANGLE_MAX`] constant for conversion calculations
178    ///
179    /// For integer degree conversion, use [`Self::angle_degrees`]
180    ///
181    /// # Errors
182    ///
183    /// Returns an error if SPI communication fails, parity check fails, or the sensor reports an error
184    pub async fn angle(&mut self) -> Result<u16, Error<E>> {
185        self.read_register(Register::Angle).await
186    }
187
188    /// Get the angular position in degrees (0-359)
189    ///
190    /// This method converts the raw 14-bit angle value to degrees using
191    /// integer arithmetic with saturation. The result is rounded down
192    ///
193    /// # Errors
194    ///
195    /// Returns an error if SPI communication fails, parity check fails, or the sensor reports an error
196    pub async fn angle_degrees(&mut self) -> Result<u16, Error<E>> {
197        let angle = self.angle().await?;
198        let degrees = (u32::from(angle).saturating_mul(360)) / u32::from(ANGLE_MAX);
199        #[allow(clippy::cast_possible_truncation)]
200        Ok(degrees as u16)
201    }
202
203    /// Get the 14-bit magnitude value from CORDIC
204    ///
205    /// Useful for checking magnet presence and strength
206    ///
207    /// # Errors
208    ///
209    /// Returns an error if SPI communication fails, parity check fails, or the sensor reports an error
210    pub async fn magnitude(&mut self) -> Result<u16, Error<E>> {
211        self.read_register(Register::Magnitude).await
212    }
213
214    /// Get the diagnostics and AGC register
215    ///
216    /// Returns a `Diagnostics` struct with helper methods to check:
217    /// - Magnetic field strength (`COMP_HIGH`, `COMP_LOW`)
218    /// - CORDIC overflow
219    /// - Offset compensation status
220    /// - AGC value
221    ///
222    /// # Errors
223    ///
224    /// Returns an error if SPI communication fails, parity check fails, or the sensor reports an error
225    ///
226    /// # Example
227    ///
228    /// ```no_run
229    /// # use as5048a_async::As5048a;
230    /// # async fn example<SPI, E>(mut sensor: As5048a<SPI>) -> Result<(), as5048a_async::Error<E>>
231    /// # where SPI: embedded_hal_async::spi::SpiDevice<u8, Error = E>
232    /// # {
233    /// let diag = sensor.diagnostics().await?;
234    ///
235    /// if diag.is_valid() {
236    ///     println!("AGC value: {}", diag.agc_value());
237    /// } else if diag.cordic_overflow() {
238    ///     println!("CORDIC overflow - data invalid!");
239    /// } else if diag.comp_high() {
240    ///     println!("Magnet too close");
241    /// } else if diag.comp_low() {
242    ///     println!("Magnet too far");
243    /// }
244    /// # Ok(())
245    /// # }
246    /// ```
247    pub async fn diagnostics(&mut self) -> Result<Diagnostics, Error<E>> {
248        self.read_register(Register::DiagAgc)
249            .await
250            .map(Diagnostics::new)
251    }
252
253    /// Clear the error flag by reading the clear error flag register
254    ///
255    /// # Errors
256    ///
257    /// Returns an error if SPI communication fails, parity check fails, or the sensor reports an error
258    pub async fn clear_error_flag(&mut self) -> Result<(), Error<E>> {
259        self.read_register(Register::ClearErrorFlag).await?;
260        Ok(())
261    }
262}