omnitiles/drivers/
drv8873.rs

1// SPDX-License-Identifier: MIT
2// © 2025–2026 Christopher Liu
3
4//! DRV8873-Q1 SPI motor driver.
5//!
6//! This module handles SPI framing and register access for DRV8873-Q1. Higher-level motor control
7//! can be layered on top of these primitives.
8
9use crate::hw::{ChipSelect, SpiBus};
10use stm32f7xx_hal::spi;
11
12// Register addresses
13pub mod reg {
14    pub const FAULT: u8 = 0x00;
15    pub const DIAG: u8 = 0x01;
16    pub const IC1: u8 = 0x02;
17    pub const IC2: u8 = 0x03;
18    pub const IC3: u8 = 0x04;
19    pub const IC4: u8 = 0x05;
20}
21
22/// Status byte returned in the upper 8 bits of SDO.
23#[derive(Copy, Clone, Debug)]
24pub struct Status {
25    raw: u8,
26}
27
28impl Status {
29    #[inline]
30    pub fn raw(&self) -> u8 {
31        self.raw
32    }
33
34    /// Overtemperature warning.
35    #[inline]
36    pub fn otw(&self) -> bool {
37        (self.raw & (1 << 5)) != 0
38    }
39
40    /// UVLO fault condition.
41    #[inline]
42    pub fn uvlo(&self) -> bool {
43        (self.raw & (1 << 4)) != 0
44    }
45
46    /// Charge-pump undervoltage fault condition.
47    #[inline]
48    pub fn cpuv(&self) -> bool {
49        (self.raw & (1 << 3)) != 0
50    }
51
52    /// Overcurrent condition.
53    #[inline]
54    pub fn ocp(&self) -> bool {
55        (self.raw & (1 << 2)) != 0
56    }
57
58    /// Overcurrent shutdown.
59    #[inline]
60    pub fn tsd(&self) -> bool {
61        (self.raw & (1 << 1)) != 0
62    }
63
64    /// Open-load detection.
65    #[inline]
66    pub fn old(&self) -> bool {
67        (self.raw & (1 << 0)) != 0
68    }
69}
70
71/// FAULT status register.
72#[derive(Copy, Clone, Debug)]
73pub struct Fault {
74    raw: u8,
75}
76
77impl Fault {
78    #[inline]
79    pub fn raw(&self) -> u8 {
80        self.raw
81    }
82
83    /// Global FAULT status register. Complements the nFAULT pin.
84    #[inline]
85    pub fn fault(&self) -> bool {
86        (self.raw & (1 << 6)) != 0
87    }
88
89    /// Overtemperature warning.
90    #[inline]
91    pub fn otw(&self) -> bool {
92        (self.raw & (1 << 5)) != 0
93    }
94
95    /// UVLO fault condition.
96    #[inline]
97    pub fn uvlo(&self) -> bool {
98        (self.raw & (1 << 4)) != 0
99    }
100
101    /// Charge-pump undervoltage fault condition.
102    #[inline]
103    pub fn cpuv(&self) -> bool {
104        (self.raw & (1 << 3)) != 0
105    }
106
107    /// Overcurrent condition.
108    #[inline]
109    pub fn ocp(&self) -> bool {
110        (self.raw & (1 << 2)) != 0
111    }
112
113    /// Overcurrent shutdown.
114    #[inline]
115    pub fn tsd(&self) -> bool {
116        (self.raw & (1 << 1)) != 0
117    }
118
119    /// Open-load detection.
120    #[inline]
121    pub fn old(&self) -> bool {
122        (self.raw & (1 << 0)) != 0
123    }
124}
125
126/// DIAG status register.
127#[derive(Copy, Clone, Debug)]
128pub struct Diag {
129    raw: u8,
130}
131
132impl Diag {
133    #[inline]
134    pub fn raw(&self) -> u8 {
135        self.raw
136    }
137
138    /// Open-load detection on half bridge 1.
139    #[inline]
140    pub fn ol1(&self) -> bool {
141        (self.raw & (1 << 7)) != 0
142    }
143
144    /// Open-load detection on half bridge 2.
145    #[inline]
146    pub fn ol2(&self) -> bool {
147        (self.raw & (1 << 6)) != 0
148    }
149
150    /// Current regulation status of half bridge 1.
151    ///
152    /// 1 indicates output 1 is in current regulation, 0 indicates it is not.
153    #[inline]
154    pub fn itrip1(&self) -> bool {
155        (self.raw & (1 << 5)) != 0
156    }
157
158    /// Current regulation status of half bridge 2.
159    ///
160    /// 1 indicates output 2 is in current regulation, 0 indicates it is not.
161    #[inline]
162    pub fn itrip2(&self) -> bool {
163        (self.raw & (1 << 4)) != 0
164    }
165
166    /// Overcurrent fault on the high-side FET of half bridge 1.
167    #[inline]
168    pub fn ocp_h1(&self) -> bool {
169        (self.raw & (1 << 3)) != 0
170    }
171
172    /// Overcurrent fault on the low-side FET of half bridge 1.
173    #[inline]
174    pub fn ocp_l1(&self) -> bool {
175        (self.raw & (1 << 2)) != 0
176    }
177
178    /// Overcurrent fault on the high-side FET of half bridge 2.
179    #[inline]
180    pub fn ocp_h2(&self) -> bool {
181        (self.raw & (1 << 1)) != 0
182    }
183
184    /// Overcurrent fault on the low-side FET of half bridge 2.
185    #[inline]
186    pub fn ocp_l2(&self) -> bool {
187        (self.raw & 1) != 0
188    }
189}
190
191/// Response of a single SPI transaction:
192/// - status byte (fault/warning flags)
193/// - data byte (register contents)
194#[derive(Copy, Clone, Debug)]
195pub struct Response {
196    pub status: Status,
197    pub data: u8,
198}
199
200/// DRV8873 driver bound to a specific chip-select pin.
201///
202/// The SPI bus is passed in as &mut to each method so that multiple DRV8873 instances can share the
203/// same bus.
204pub struct Drv8873<const P: char, const N: u8> {
205    cs: ChipSelect<P, N>,
206}
207
208impl<const P: char, const N: u8> Drv8873<P, N> {
209    /// Construct a driver from an active-low chip-select pin.
210    pub fn new(cs: ChipSelect<P, N>) -> Self {
211        Self { cs }
212    }
213
214    /// Release the chip-select pin.
215    pub fn free(self) -> ChipSelect<P, N> {
216        self.cs
217    }
218
219    /// Build a 16-bit SPI word for this device.
220    /// - `is_read`: true for read, false for write
221    /// - `addr`: 5-bit register address
222    /// - `data`: 8-bit data payload (ignored for reads by the device)
223    #[inline]
224    fn build_word(is_read: bool, addr: u8, data: u8) -> u16 {
225        let mut word: u16 = 0;
226
227        // B15 = 0
228        // B14 = W (1 = read, 0 = write)
229        if is_read {
230            word |= 1 << 14;
231        }
232
233        // B13..B9 = A4..A0 (5-bit addr)
234        word |= ((addr as u16) & 0x1F) << 9;
235
236        // B8 = X
237        // B7..B0 = data
238        word |= data as u16;
239
240        word
241    }
242
243    /// Send a 16-bit word and receive the status + data bytes.
244    /// - `spi` must be an enabled 8-bit SPI bus configured in the correct mode
245    ///   for the DRV8873 (CPOL=0, CPHA=1: data captured on falling edge, driven on rising edge).
246    pub fn transfer_word<I, PINS>(
247        &mut self,
248        spi: &mut SpiBus<I, PINS>,
249        word: u16,
250    ) -> Result<Response, spi::Error>
251    where
252        I: spi::Instance,
253        PINS: spi::Pins<I>,
254    {
255        let mut buf = [(word >> 8) as u8, word as u8];
256
257        self.cs.select();
258        spi.transfer_in_place(&mut buf)?;
259        self.cs.deselect();
260
261        let status = Status { raw: buf[0] };
262        let data = buf[1];
263
264        Ok(Response { status, data })
265    }
266
267    /// Write a register and return the response (status + current register contents).
268    pub fn write_reg<I, PINS>(
269        &mut self,
270        spi: &mut SpiBus<I, PINS>,
271        addr: u8,
272        value: u8,
273    ) -> Result<Response, spi::Error>
274    where
275        I: spi::Instance,
276        PINS: spi::Pins<I>,
277    {
278        let word = Self::build_word(false, addr, value);
279        self.transfer_word(spi, word)
280    }
281
282    /// Read a register and return the response (status + register value).
283    pub fn read_reg<I, PINS>(
284        &mut self,
285        spi: &mut SpiBus<I, PINS>,
286        addr: u8,
287    ) -> Result<Response, spi::Error>
288    where
289        I: spi::Instance,
290        PINS: spi::Pins<I>,
291    {
292        let word = Self::build_word(true, addr, 0x00);
293        self.transfer_word(spi, word)
294    }
295
296    /// Read the FAULT register and parse into a `Fault` struct.
297    ///
298    /// To get the status result as well, use `read_reg`.
299    pub fn read_fault<I, PINS>(&mut self, spi: &mut SpiBus<I, PINS>) -> Result<Fault, spi::Error>
300    where
301        I: spi::Instance,
302        PINS: spi::Pins<I>,
303    {
304        Ok(Fault {
305            raw: self.read_reg(spi, reg::FAULT)?.data,
306        })
307    }
308
309    /// Read the DIAG register and parse into a `Diag` struct.
310    ///
311    /// To get the status result as well, use `read_reg`.
312    pub fn read_diag<I, PINS>(&mut self, spi: &mut SpiBus<I, PINS>) -> Result<Diag, spi::Error>
313    where
314        I: spi::Instance,
315        PINS: spi::Pins<I>,
316    {
317        Ok(Diag {
318            raw: self.read_reg(spi, reg::DIAG)?.data,
319        })
320    }
321}