pamoja_gpio/i2c.rs
1//! I2C device addressing per the NXP I2C-bus specification (UM10204).
2//!
3//! An I2C transfer begins with the controller sending the device's address. The exact
4//! bytes are pinned down by the specification, and they are easy to get subtly wrong: the
5//! 7-bit address shares its byte with the read/write bit, so the value a datasheet prints
6//! is not the byte that goes on the wire, and the 10-bit extension spends a reserved
7//! prefix and spreads its bits across two bytes. This module builds those bytes exactly
8//! and rejects an address that is out of range or, on request, one the specification
9//! reserves.
10
11use crate::GpioError;
12
13/// The lowest 7-bit address the specification keeps for itself.
14///
15/// Addresses from here up are reserved: `0x78` to `0x7B` introduce a 10-bit address and
16/// the rest are held back, so a part answering in this range is a bus feature rather than
17/// a device a driver talks to. [`Address::is_reserved`] covers this range and
18/// [`RESERVED_BELOW`].
19pub const RESERVED_FROM: u8 = 0x78;
20
21/// The first 7-bit address above the reserved block at the bottom of the range.
22///
23/// Everything below this, `0x00` to `0x07`, is reserved: `0x00` is the general call every
24/// device listens to, and the rest carry bus functions.
25pub const RESERVED_BELOW: u8 = 0x08;
26
27/// Largest valid 7-bit address (inclusive).
28const MAX_SEVEN_BIT: u16 = 0x7F;
29/// Largest valid 10-bit address (inclusive).
30const MAX_TEN_BIT: u16 = 0x3FF;
31/// The five-bit `11110` prefix (in the top of the first byte) that marks a 10-bit address.
32const TEN_BIT_PREFIX: u8 = 0xF0;
33
34/// Whether an I2C transfer reads from or writes to the device.
35///
36/// The direction rides in the least-significant bit of the address byte: `0` for a write,
37/// `1` for a read.
38#[derive(Clone, Copy, Debug, PartialEq, Eq)]
39pub enum Direction {
40 /// The controller writes to the device. R/W bit `0`.
41 Write,
42 /// The controller reads from the device. R/W bit `1`.
43 Read,
44}
45
46impl Direction {
47 /// Returns the R/W bit this direction places in the low bit of the address byte.
48 ///
49 /// # Returns
50 ///
51 /// `0` for [`Write`](Direction::Write), `1` for [`Read`](Direction::Read).
52 pub fn rw_bit(self) -> u8 {
53 match self {
54 Direction::Write => 0,
55 Direction::Read => 1,
56 }
57 }
58}
59
60/// An I2C device address, 7-bit or 10-bit, validated to its range.
61///
62/// I2C addresses come in two widths. The original 7-bit address shares its byte with the
63/// R/W bit, so it lands on the wire as `(address << 1) | r/w`. The later 10-bit extension
64/// stays backward compatible by spending the reserved `11110xx` prefix: the first byte is
65/// `11110`, then the top two address bits, then the R/W bit, and the second byte is the
66/// low eight address bits. Construct an address with [`seven_bit`](Address::seven_bit) or
67/// [`ten_bit`](Address::ten_bit), which reject out-of-range values, then turn it into the
68/// bytes a controller sends with [`write_frame`](Address::write_frame).
69///
70/// # Examples
71///
72/// ```
73/// use pamoja_gpio::i2c::{Address, Direction};
74///
75/// // 7-bit: a BME280 at 0x76 writes as 0xEC and reads as 0xED.
76/// let bme = Address::seven_bit(0x76)?;
77/// let mut buf = [0u8; 2];
78/// assert_eq!(bme.write_frame(Direction::Write, &mut buf)?, 1);
79/// assert_eq!(buf[0], 0xEC);
80/// assert_eq!(bme.write_frame(Direction::Read, &mut buf)?, 1);
81/// assert_eq!(buf[0], 0xED);
82/// # Ok::<(), pamoja_gpio::GpioError>(())
83/// ```
84#[derive(Clone, Copy, Debug, PartialEq, Eq)]
85pub struct Address {
86 value: u16,
87 ten_bit: bool,
88}
89
90impl Address {
91 /// Creates a 7-bit I2C address.
92 ///
93 /// The whole range is accepted, including the addresses the specification reserves;
94 /// those are still legal on the wire (the general call address `0x00` is a broadcast,
95 /// for instance). Use [`is_reserved`](Address::is_reserved) to test for them.
96 ///
97 /// # Arguments
98 ///
99 /// * `address` - the 7-bit device address, `0x00..=0x7F`.
100 ///
101 /// # Returns
102 ///
103 /// The validated address.
104 ///
105 /// # Errors
106 ///
107 /// [`GpioError::AddressOutOfRange`] if `address` exceeds `0x7F`.
108 pub fn seven_bit(address: u8) -> Result<Address, GpioError> {
109 if address as u16 > MAX_SEVEN_BIT {
110 return Err(GpioError::AddressOutOfRange);
111 }
112 Ok(Address {
113 value: address as u16,
114 ten_bit: false,
115 })
116 }
117
118 /// Creates a 10-bit I2C address.
119 ///
120 /// # Arguments
121 ///
122 /// * `address` - the 10-bit device address, `0x000..=0x3FF`.
123 ///
124 /// # Returns
125 ///
126 /// The validated address.
127 ///
128 /// # Errors
129 ///
130 /// [`GpioError::AddressOutOfRange`] if `address` exceeds `0x3FF`.
131 pub fn ten_bit(address: u16) -> Result<Address, GpioError> {
132 if address > MAX_TEN_BIT {
133 return Err(GpioError::AddressOutOfRange);
134 }
135 Ok(Address {
136 value: address,
137 ten_bit: true,
138 })
139 }
140
141 /// Returns the address value, without the R/W bit.
142 ///
143 /// # Returns
144 ///
145 /// The 7- or 10-bit address as passed to the constructor.
146 pub fn value(self) -> u16 {
147 self.value
148 }
149
150 /// Returns `true` if this is a 10-bit address.
151 pub fn is_ten_bit(self) -> bool {
152 self.ten_bit
153 }
154
155 /// Returns the number of bytes [`write_frame`](Address::write_frame) emits.
156 ///
157 /// # Returns
158 ///
159 /// `1` for a 7-bit address, `2` for a 10-bit address.
160 pub fn frame_len(self) -> usize {
161 if self.ten_bit {
162 2
163 } else {
164 1
165 }
166 }
167
168 /// Returns `true` if a 7-bit address falls in a range the I2C specification reserves.
169 ///
170 /// UM10204 reserves `0x00..=0x07` (general call and START byte, CBUS, a bus-format
171 /// code, a future code, and the Hs-mode master codes) and `0x78..=0x7F` (the 10-bit
172 /// addressing prefix and the device-ID codes), leaving `0x08..=0x77` for ordinary
173 /// devices. A 10-bit address is not reserved in this sense, so this returns `false`
174 /// for one.
175 ///
176 /// # Returns
177 ///
178 /// `true` if this is a 7-bit address in `0x00..=0x07` or `0x78..=0x7F`.
179 pub fn is_reserved(self) -> bool {
180 !self.ten_bit
181 && (self.value < u16::from(RESERVED_BELOW) || self.value >= u16::from(RESERVED_FROM))
182 }
183
184 /// Returns `true` if this is the general call address `0x00`, the broadcast every
185 /// device on the bus listens to.
186 pub fn is_general_call(self) -> bool {
187 !self.ten_bit && self.value == 0x00
188 }
189
190 /// Returns the addressing frame this address puts on the bus.
191 ///
192 /// The same bytes [`write_frame`](Address::write_frame) produces, as a value, so a
193 /// caller does not have to size and pass a scratch buffer to find out what an address
194 /// looks like on the wire.
195 ///
196 /// # Arguments
197 ///
198 /// * `direction` - whether the transfer reads or writes, which sets the R/W bit.
199 ///
200 /// # Returns
201 ///
202 /// The frame, one byte for a 7-bit address and two for a 10-bit one.
203 pub fn frame(self, direction: Direction) -> AddressFrame {
204 let mut bytes = [0u8; 2];
205 let len = self.write_frame(direction, &mut bytes).unwrap_or_default();
206 AddressFrame { bytes, len }
207 }
208
209 /// Writes the address byte(s) a controller puts on the bus for a transfer.
210 ///
211 /// For a 7-bit address this is the single byte `(address << 1) | r/w`. For a 10-bit
212 /// address it is two bytes: `11110` then the top two address bits then the R/W bit,
213 /// followed by the low eight address bits. A 10-bit read in practice first addresses
214 /// the device with a write frame and then, after a repeated START, re-sends this first
215 /// byte with the read bit set; this method emits the bytes for the `direction` asked
216 /// for, leaving the START/repeated-START sequencing to the driver.
217 ///
218 /// # Arguments
219 ///
220 /// * `direction` - whether the transfer reads or writes, which sets the R/W bit.
221 /// * `out` - the buffer the frame is written into; it must hold at least
222 /// [`frame_len`](Address::frame_len) bytes.
223 ///
224 /// # Returns
225 ///
226 /// The number of bytes written: `1` for a 7-bit address, `2` for a 10-bit address.
227 ///
228 /// # Errors
229 ///
230 /// [`GpioError::BufferTooSmall`] if `out` is shorter than [`frame_len`](Address::frame_len).
231 pub fn write_frame(self, direction: Direction, out: &mut [u8]) -> Result<usize, GpioError> {
232 let rw = direction.rw_bit();
233 if self.ten_bit {
234 if out.len() < 2 {
235 return Err(GpioError::BufferTooSmall);
236 }
237 let high = ((self.value >> 8) as u8) & 0x03;
238 out[0] = TEN_BIT_PREFIX | (high << 1) | rw;
239 out[1] = (self.value & 0xFF) as u8;
240 Ok(2)
241 } else {
242 if out.is_empty() {
243 return Err(GpioError::BufferTooSmall);
244 }
245 out[0] = ((self.value as u8) << 1) | rw;
246 Ok(1)
247 }
248 }
249}
250
251/// The bytes an [`Address`] puts on the bus to address a device.
252///
253/// One byte for a 7-bit address and two for a 10-bit one, held inline so producing one
254/// allocates nothing and needs no caller-supplied buffer.
255#[derive(Clone, Copy, Debug, PartialEq, Eq)]
256pub struct AddressFrame {
257 bytes: [u8; 2],
258 len: usize,
259}
260
261impl AddressFrame {
262 /// Returns the frame bytes, in the order they go on the bus.
263 ///
264 /// # Returns
265 ///
266 /// One byte for a 7-bit address, two for a 10-bit one.
267 pub fn as_bytes(&self) -> &[u8] {
268 &self.bytes[..self.len]
269 }
270
271 /// Returns how many bytes the frame occupies.
272 ///
273 /// # Returns
274 ///
275 /// `1` for a 7-bit address, `2` for a 10-bit one.
276 pub fn len(&self) -> usize {
277 self.len
278 }
279
280 /// Returns whether the frame is empty, which it never is.
281 ///
282 /// # Returns
283 ///
284 /// Always `false`; an address always addresses something.
285 pub fn is_empty(&self) -> bool {
286 self.len == 0
287 }
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293
294 /// Builds the frame and asserts it equals the expected bytes on the wire.
295 fn assert_frame(address: Address, direction: Direction, expected: &[u8]) {
296 let mut buf = [0u8; 2];
297 let n = address.write_frame(direction, &mut buf).unwrap();
298 assert_eq!(&buf[..n], expected);
299 }
300
301 #[test]
302 fn seven_bit_frames_match_known_device_bytes() {
303 // Reference bytes printed on real datasheets, where the 7-bit address shifts up
304 // one and the R/W bit fills the low bit.
305 // DS3231 RTC / MPU-6050 at 0x68.
306 assert_frame(Address::seven_bit(0x68).unwrap(), Direction::Write, &[0xD0]);
307 assert_frame(Address::seven_bit(0x68).unwrap(), Direction::Read, &[0xD1]);
308 // SSD1306 OLED at 0x3C.
309 assert_frame(Address::seven_bit(0x3C).unwrap(), Direction::Write, &[0x78]);
310 assert_frame(Address::seven_bit(0x3C).unwrap(), Direction::Read, &[0x79]);
311 // AT24C32 EEPROM / PCF8574 at 0x50.
312 assert_frame(Address::seven_bit(0x50).unwrap(), Direction::Write, &[0xA0]);
313 assert_frame(Address::seven_bit(0x50).unwrap(), Direction::Read, &[0xA1]);
314 }
315
316 #[test]
317 fn ten_bit_frame_matches_spec_worked_example() {
318 // UM10204's worked 10-bit example, address 0x2A5 = 0b10_1010_0101:
319 // first byte 11110 10 0 = 0xF4, second byte 1010_0101 = 0xA5.
320 let addr = Address::ten_bit(0x2A5).unwrap();
321 assert_frame(addr, Direction::Write, &[0xF4, 0xA5]);
322 assert_frame(addr, Direction::Read, &[0xF5, 0xA5]);
323 }
324
325 #[test]
326 fn ten_bit_frame_at_the_range_bounds() {
327 // 0x000: prefix only, low byte zero.
328 assert_frame(
329 Address::ten_bit(0x000).unwrap(),
330 Direction::Write,
331 &[0xF0, 0x00],
332 );
333 // 0x3FF: top two bits set (11110 11 r/w), low byte all ones.
334 assert_frame(
335 Address::ten_bit(0x3FF).unwrap(),
336 Direction::Write,
337 &[0xF6, 0xFF],
338 );
339 assert_frame(
340 Address::ten_bit(0x3FF).unwrap(),
341 Direction::Read,
342 &[0xF7, 0xFF],
343 );
344 }
345
346 #[test]
347 fn out_of_range_addresses_are_rejected() {
348 assert_eq!(Address::seven_bit(0x80), Err(GpioError::AddressOutOfRange));
349 assert_eq!(Address::ten_bit(0x400), Err(GpioError::AddressOutOfRange));
350 // The top of each range is accepted.
351 assert!(Address::seven_bit(0x7F).is_ok());
352 assert!(Address::ten_bit(0x3FF).is_ok());
353 }
354
355 #[test]
356 fn reserved_ranges_match_the_spec() {
357 // Reserved: 0x00..=0x07 and 0x78..=0x7F.
358 for addr in (0x00..=0x07).chain(0x78..=0x7F) {
359 assert!(
360 Address::seven_bit(addr).unwrap().is_reserved(),
361 "{addr:#04x}"
362 );
363 }
364 // The usable range is everything between.
365 for addr in 0x08..=0x77 {
366 assert!(
367 !Address::seven_bit(addr).unwrap().is_reserved(),
368 "{addr:#04x}"
369 );
370 }
371 // A 10-bit address is never reserved in the 7-bit sense.
372 assert!(!Address::ten_bit(0x002).unwrap().is_reserved());
373 }
374
375 #[test]
376 fn general_call_is_address_zero() {
377 assert!(Address::seven_bit(0x00).unwrap().is_general_call());
378 assert!(!Address::seven_bit(0x01).unwrap().is_general_call());
379 assert!(!Address::ten_bit(0x000).unwrap().is_general_call());
380 }
381
382 #[test]
383 fn frame_len_and_too_small_buffers() {
384 assert_eq!(Address::seven_bit(0x40).unwrap().frame_len(), 1);
385 assert_eq!(Address::ten_bit(0x100).unwrap().frame_len(), 2);
386
387 let mut empty = [];
388 assert_eq!(
389 Address::seven_bit(0x40)
390 .unwrap()
391 .write_frame(Direction::Write, &mut empty),
392 Err(GpioError::BufferTooSmall)
393 );
394 let mut one = [0u8; 1];
395 assert_eq!(
396 Address::ten_bit(0x100)
397 .unwrap()
398 .write_frame(Direction::Write, &mut one),
399 Err(GpioError::BufferTooSmall)
400 );
401 }
402}