Skip to main content

pamoja_sensors/
driver.rs

1//! What the drivers share: how a register-mapped part is reached over a bus.
2//!
3//! Bosch's BME280 and BMP280 speak one register protocol over two buses, I2C and SPI.
4//! [`RegisterBus`] names the two operations that protocol needs, a multi-byte read
5//! starting at a register and a single-byte write, and [`I2cRegisters`] and
6//! [`SpiRegisters`] implement them: over I2C the register address is written and the
7//! data read back in one transaction, over SPI bit 7 of the control byte is the
8//! read/write flag and the address increments on its own, as the BME280 datasheet's
9//! interface chapter lays out. A driver written against the trait runs on either bus.
10//!
11//! Texas Instruments' parts keep 16-bit registers that travel most significant byte
12//! first over I2C; [`read_word`] and [`write_word`] are that shape.
13
14use embedded_hal::i2c::I2c;
15use embedded_hal::spi::{Operation, SpiDevice};
16
17/// The read/write flag in a Bosch SPI control byte: set to read, clear to write.
18pub const SPI_READ: u8 = 0x80;
19
20/// The mask that leaves the seven address bits of a Bosch SPI control byte.
21pub const SPI_ADDRESS_MASK: u8 = 0x7F;
22
23/// A part's register map reached over some bus.
24///
25/// # Examples
26///
27/// ```
28/// use pamoja_hal::script::{I2cScript, I2cStep};
29/// use pamoja_sensors::bme280::{register, CHIP_ID, I2C_ADDRESS_PRIMARY, RESET_WORD};
30/// use pamoja_sensors::driver::{I2cRegisters, RegisterBus};
31///
32/// // A BME280 answers its chip id, then takes the word that resets it.
33/// const PART: u8 = I2C_ADDRESS_PRIMARY;
34/// let script = I2cScript::new([
35///     I2cStep::write_read(PART, [register::CHIP_ID], [CHIP_ID]),
36///     I2cStep::write(PART, [register::RESET, RESET_WORD]),
37/// ]);
38/// let mut part = I2cRegisters::new(script, PART);
39/// assert_eq!(part.read_register(register::CHIP_ID)?, CHIP_ID);
40/// part.write_register(register::RESET, RESET_WORD)?;
41/// assert!(part.release().done());
42/// # Ok::<(), pamoja_hal::script::ScriptError>(())
43/// ```
44pub trait RegisterBus {
45    /// The error the bus underneath reports.
46    type Error;
47
48    /// Reads consecutive registers starting at `first`, as many as `buffer` holds.
49    ///
50    /// # Arguments
51    ///
52    /// * `first` - the address of the first register.
53    /// * `buffer` - where the register values go, one byte per register.
54    ///
55    /// # Errors
56    ///
57    /// Returns the bus error if the part does not answer.
58    fn read_registers(&mut self, first: u8, buffer: &mut [u8]) -> Result<(), Self::Error>;
59
60    /// Writes one register.
61    ///
62    /// # Arguments
63    ///
64    /// * `register` - the register address.
65    /// * `value` - the byte to write.
66    ///
67    /// # Errors
68    ///
69    /// Returns the bus error if the part does not answer.
70    fn write_register(&mut self, register: u8, value: u8) -> Result<(), Self::Error>;
71
72    /// Reads one register.
73    ///
74    /// # Arguments
75    ///
76    /// * `register` - the register address.
77    ///
78    /// # Returns
79    ///
80    /// The register's byte.
81    ///
82    /// # Errors
83    ///
84    /// Returns the bus error if the part does not answer.
85    fn read_register(&mut self, register: u8) -> Result<u8, Self::Error> {
86        let mut byte = [0u8; 1];
87        self.read_registers(register, &mut byte)?;
88        Ok(byte[0])
89    }
90}
91
92/// A register map behind an I2C address.
93///
94/// A read writes the register address and reads the values back in one transaction,
95/// with a repeated start between them; a write sends the address and the value.
96#[derive(Debug)]
97pub struct I2cRegisters<I2C> {
98    bus: I2C,
99    address: u8,
100}
101
102impl<I2C> I2cRegisters<I2C> {
103    /// Wraps an I2C bus and the part's 7-bit address.
104    ///
105    /// # Arguments
106    ///
107    /// * `bus` - the I2C bus the part is on.
108    /// * `address` - the part's 7-bit address.
109    ///
110    /// # Returns
111    ///
112    /// The register map.
113    pub fn new(bus: I2C, address: u8) -> I2cRegisters<I2C> {
114        I2cRegisters { bus, address }
115    }
116
117    /// Returns the part's 7-bit address.
118    pub fn address(&self) -> u8 {
119        self.address
120    }
121
122    /// Gives back the bus.
123    ///
124    /// # Returns
125    ///
126    /// The bus the map was built from.
127    pub fn release(self) -> I2C {
128        self.bus
129    }
130}
131
132impl<I2C: I2c> RegisterBus for I2cRegisters<I2C> {
133    type Error = I2C::Error;
134
135    fn read_registers(&mut self, first: u8, buffer: &mut [u8]) -> Result<(), Self::Error> {
136        self.bus.write_read(self.address, &[first], buffer)
137    }
138
139    fn write_register(&mut self, register: u8, value: u8) -> Result<(), Self::Error> {
140        self.bus.write(self.address, &[register, value])
141    }
142}
143
144/// A register map behind an SPI chip select, in the Bosch framing.
145///
146/// The first byte of every transfer is the control byte: the register address with
147/// bit 7 set to read or cleared to write. On a read the part then streams consecutive
148/// registers; on a write the value follows. The part accepts SPI modes 0 and 3.
149#[derive(Debug)]
150pub struct SpiRegisters<SPI> {
151    device: SPI,
152}
153
154impl<SPI> SpiRegisters<SPI> {
155    /// Wraps an SPI device, which is the bus plus the part's chip-select line.
156    ///
157    /// # Arguments
158    ///
159    /// * `device` - the SPI device.
160    ///
161    /// # Returns
162    ///
163    /// The register map.
164    pub fn new(device: SPI) -> SpiRegisters<SPI> {
165        SpiRegisters { device }
166    }
167
168    /// Gives back the device.
169    ///
170    /// # Returns
171    ///
172    /// The device the map was built from.
173    pub fn release(self) -> SPI {
174        self.device
175    }
176}
177
178impl<SPI: SpiDevice> RegisterBus for SpiRegisters<SPI> {
179    type Error = SPI::Error;
180
181    fn read_registers(&mut self, first: u8, buffer: &mut [u8]) -> Result<(), Self::Error> {
182        let control = [first | SPI_READ];
183        self.device
184            .transaction(&mut [Operation::Write(&control), Operation::Read(buffer)])
185    }
186
187    fn write_register(&mut self, register: u8, value: u8) -> Result<(), Self::Error> {
188        self.device.write(&[register & SPI_ADDRESS_MASK, value])
189    }
190}
191
192/// Reads a 16-bit register that travels most significant byte first.
193///
194/// # Arguments
195///
196/// * `bus` - the I2C bus.
197/// * `address` - the part's 7-bit address.
198/// * `register` - the register address, or pointer value, to read.
199///
200/// # Returns
201///
202/// The register's value.
203///
204/// # Errors
205///
206/// Returns the bus error if the part does not answer.
207///
208/// # Examples
209///
210/// ```
211/// use pamoja_hal::script::{I2cScript, I2cStep};
212/// use pamoja_sensors::driver::read_word;
213/// use pamoja_sensors::tmp117::{address, register, DEVICE_ID};
214///
215/// // A TMP117 reads its device id register as one word, high byte first.
216/// const PART: u8 = address::ADD0_GND;
217/// let answer = DEVICE_ID.to_be_bytes();
218/// let mut bus = I2cScript::new([I2cStep::write_read(PART, [register::DEVICE_ID], answer)]);
219/// assert_eq!(read_word(&mut bus, PART, register::DEVICE_ID)?, DEVICE_ID);
220/// # Ok::<(), pamoja_hal::script::ScriptError>(())
221/// ```
222pub fn read_word<I2C: I2c>(bus: &mut I2C, address: u8, register: u8) -> Result<u16, I2C::Error> {
223    let mut bytes = [0u8; 2];
224    bus.write_read(address, &[register], &mut bytes)?;
225    Ok(u16::from_be_bytes(bytes))
226}
227
228/// Writes a 16-bit register that travels most significant byte first.
229///
230/// # Arguments
231///
232/// * `bus` - the I2C bus.
233/// * `address` - the part's 7-bit address.
234/// * `register` - the register address, or pointer value, to write.
235/// * `value` - the value to write.
236///
237/// # Errors
238///
239/// Returns the bus error if the part does not answer.
240pub fn write_word<I2C: I2c>(
241    bus: &mut I2C,
242    address: u8,
243    register: u8,
244    value: u16,
245) -> Result<(), I2C::Error> {
246    let [high, low] = value.to_be_bytes();
247    bus.write(address, &[register, high, low])
248}
249
250#[cfg(test)]
251mod tests {
252    use super::*;
253    use pamoja_hal::script::{I2cScript, I2cStep, SpiScript, SpiStep};
254
255    #[test]
256    fn i2c_registers_read_with_a_repeated_start_and_write_address_then_value() {
257        let script = I2cScript::new([
258            I2cStep::write_read(0x77, [0xF7], [1, 2, 3, 4, 5, 6, 7, 8]),
259            I2cStep::write(0x77, [0xF4, 0x25]),
260        ]);
261        let mut part = I2cRegisters::new(script, 0x77);
262        let mut data = [0u8; 8];
263        part.read_registers(0xF7, &mut data).unwrap();
264        assert_eq!(data, [1, 2, 3, 4, 5, 6, 7, 8]);
265        part.write_register(0xF4, 0x25).unwrap();
266        assert_eq!(part.address(), 0x77);
267        assert!(part.release().done());
268    }
269
270    #[test]
271    fn spi_registers_set_the_read_flag_and_clear_it_to_write() {
272        let script = SpiScript::new([
273            SpiStep::write([0xD0]),
274            SpiStep::read([0x60]),
275            SpiStep::write([0x74, 0x25]),
276        ]);
277        let mut part = SpiRegisters::new(script);
278        assert_eq!(part.read_register(0xD0).unwrap(), 0x60);
279        part.write_register(0xF4, 0x25).unwrap();
280        assert!(part.release().done());
281    }
282
283    #[test]
284    fn words_travel_most_significant_byte_first() {
285        let mut bus = I2cScript::new([
286            I2cStep::write(0x40, [0x05, 0x10, 0x00]),
287            I2cStep::write_read(0x40, [0x02], [0x1F, 0x40]),
288        ]);
289        write_word(&mut bus, 0x40, 0x05, 0x1000).unwrap();
290        assert_eq!(read_word(&mut bus, 0x40, 0x02).unwrap(), 0x1F40);
291        assert!(bus.done());
292    }
293}