aw9523_embedded/
lib.rs

1//! AW9523 GPIO Expander Driver
2//!
3//! A platform-agnostic driver for the AW9523 16-channel LED driver and GPIO expander.
4//!
5//! The AW9523 is an I2C-controlled GPIO expander with 16 configurable pins that can operate
6//! as digital I/O or constant-current LED drivers. Each pin supports:
7//! - Digital input/output with configurable direction
8//! - Interrupt detection
9//! - 256-step constant current LED driving
10//! - Open-drain or push-pull output modes (port 0)
11//!
12//! # Features
13//!
14//! - `no_std` compatible
15//! - Uses `embedded-hal` traits for portability
16//! - Individual pin control or bulk 16-bit operations
17//! - Configurable interrupt detection per pin
18//! - LED mode with PWM-like current control
19//! - **Async support** via the `async` feature (requires `embedded-hal-async`)
20//!
21//! # Blocking Example
22//!
23//! ```ignore
24//! use aw9523::{Aw9523, PinMode, HIGH};
25//! # let i2c = todo!();
26//!
27//! // Create device with I2C bus and default address
28//! let mut gpio = Aw9523::new(i2c, 0x58);
29//!
30//! // Initialize with default configuration
31//! gpio.init().unwrap();
32//!
33//! // Configure pin 0 as output and set it high
34//! gpio.pin_mode(0, PinMode::Output).unwrap();
35//! gpio.digital_write(0, HIGH).unwrap();
36//!
37//! // Configure pin 8 as input and read it
38//! gpio.pin_mode(8, PinMode::Input).unwrap();
39//! let state = gpio.digital_read(8).unwrap();
40//! ```
41//!
42//! # Async Example
43//!
44//! Enable the `async` feature in your `Cargo.toml`:
45//! ```toml
46//! [dependencies]
47//! aw9523-embedded = { version = "0.1", features = ["async"] }
48//! ```
49//!
50//! Then use the async API:
51//! ```ignore
52//! use aw9523::{r#async::Aw9523Async, PinMode, HIGH};
53//! # let i2c = todo!(); // async I2C implementation
54//!
55//! async fn example() {
56//!     // Create device with async I2C bus
57//!     let mut gpio = Aw9523Async::new(i2c, 0x58);
58//!
59//!     // All methods are async
60//!     gpio.init().await.unwrap();
61//!     gpio.pin_mode(0, PinMode::Output).await.unwrap();
62//!     gpio.digital_write(0, HIGH).await.unwrap();
63//! }
64//! ```
65
66#![no_std]
67
68#[cfg(test)]
69#[macro_use]
70extern crate std;
71
72pub use embedded_hal::digital::PinState;
73use embedded_hal::i2c::{AddressMode, I2c};
74
75/// Default I2C address for the AW9523
76const __AW9523_ADDRESS: u8 = 0x58;
77
78// Hardware register addresses
79/// Chip ID register (read-only, returns 0x23)
80pub const AW9523_REG_CHIPID: u8 = 0x10;
81const AW9523_REG_SOFTRESET: u8 = 0x7F; // Soft reset register
82const AW9523_REG_INPUT0: u8 = 0x00; // Input port 0 (pins 0-7)
83/// Input port 1 register (pins 8-15)
84pub const AW9523_REG_INPUT1: u8 = 0x01;
85const AW9523_REG_OUTPUT0: u8 = 0x02; // Output port 0 (pins 0-7)
86const AW9523_REG_OUTPUT1: u8 = 0x03; // Output port 1 (pins 8-15)
87const AW9523_REG_CONFIG0: u8 = 0x04; // Direction config port 0
88const AW9523_REG_CONFIG1: u8 = 0x05; // Direction config port 1
89const AW9523_REG_INTENABLE0: u8 = 0x06; // Interrupt enable port 0
90/// Interrupt enable port 1 register
91pub const AW9523_REG_INTENABLE1: u8 = 0x07;
92const AW9523_REG_GCR: u8 = 0x11; // Global control register
93const AW9523_REG_LEDMODE: u8 = 0x12; // LED mode config port 0
94const AW9523_REG_LEDMODE1: u8 = 0x13; // LED mode config port 1
95
96/// Pin mode constant for LED/constant-current mode.
97/// Use with [`Aw9523::pin_mode`] to configure a pin for LED driving.
98pub const AW9523_LED_MODE: u8 = 2;
99
100// Pin bitmasks for 16-bit operations
101// Use these with output_gpio(), interrupt_enable_gpio(), configure_direction(), configure_led_mode()
102/// Bitmask for pin 0
103pub const AW9523_PIN_0: u16 = 1 << 0;
104/// Bitmask for pin 1
105pub const AW9523_PIN_1: u16 = 1 << 1;
106/// Bitmask for pin 2
107pub const AW9523_PIN_2: u16 = 1 << 2;
108/// Bitmask for pin 3
109pub const AW9523_PIN_3: u16 = 1 << 3;
110/// Bitmask for pin 4
111pub const AW9523_PIN_4: u16 = 1 << 4;
112/// Bitmask for pin 5
113pub const AW9523_PIN_5: u16 = 1 << 5;
114/// Bitmask for pin 6
115pub const AW9523_PIN_6: u16 = 1 << 6;
116/// Bitmask for pin 7
117pub const AW9523_PIN_7: u16 = 1 << 7;
118/// Bitmask for pin 8
119pub const AW9523_PIN_8: u16 = 1 << 8;
120/// Bitmask for pin 9
121pub const AW9523_PIN_9: u16 = 1 << 9;
122/// Bitmask for pin 10
123pub const AW9523_PIN_10: u16 = 1 << 10;
124/// Bitmask for pin 11
125pub const AW9523_PIN_11: u16 = 1 << 11;
126/// Bitmask for pin 12
127pub const AW9523_PIN_12: u16 = 1 << 12;
128/// Bitmask for pin 13
129pub const AW9523_PIN_13: u16 = 1 << 13;
130/// Bitmask for pin 14
131pub const AW9523_PIN_14: u16 = 1 << 14;
132/// Bitmask for pin 15
133pub const AW9523_PIN_15: u16 = 1 << 15;
134
135// Port naming aliases (port 0 = pins 0-7, port 1 = pins 8-15)
136/// Port 0, pin 0 alias for PIN_0.
137/// The AW9523 organizes its 16 pins into two ports of 8 pins each, and this is the first pin in port 0.
138pub const AW9523_P0_0: u16 = AW9523_PIN_0;
139/// Port 0, pin 1 alias for PIN_1.
140/// This is the second pin in port 0, useful when you need port-based naming conventions.
141pub const AW9523_P0_1: u16 = AW9523_PIN_1;
142/// Port 0, pin 2 alias for PIN_2.
143/// This is the third pin in port 0, sharing the same register space as other port 0 pins.
144pub const AW9523_P0_2: u16 = AW9523_PIN_2;
145/// Port 0, pin 3 alias for PIN_3.
146/// This is the fourth pin in port 0, part of the lower byte of the 16-bit pin registers.
147pub const AW9523_P0_3: u16 = AW9523_PIN_3;
148/// Port 0, pin 4 alias for PIN_4.
149/// The middle pin of port 0, this can be configured for open-drain mode along with other port 0 pins.
150pub const AW9523_P0_4: u16 = AW9523_PIN_4;
151/// Port 0, pin 5 alias for PIN_5.
152/// This pin is part of port 0, which supports open-drain output configuration via the GCR register.
153pub const AW9523_P0_5: u16 = AW9523_PIN_5;
154/// Port 0, pin 6 alias for PIN_6.
155/// One of the higher-numbered pins in port 0, accessible through the lower-byte registers.
156pub const AW9523_P0_6: u16 = AW9523_PIN_6;
157/// Port 0, pin 7 alias for PIN_7.
158/// The last pin in port 0, representing bit 7 of the port 0 register space.
159pub const AW9523_P0_7: u16 = AW9523_PIN_7;
160
161/// Port 1, pin 0 alias for PIN_8.
162/// The first pin in port 1, which always operates in push-pull mode (unlike port 0).
163pub const AW9523_P1_0: u16 = AW9523_PIN_8;
164/// Port 1, pin 1 alias for PIN_9.
165/// This is the second pin in port 1, accessed through the upper-byte registers.
166pub const AW9523_P1_1: u16 = AW9523_PIN_9;
167/// Port 1, pin 2 alias for PIN_10.
168/// The third pin in port 1, part of the upper byte of the 16-bit pin registers.
169pub const AW9523_P1_2: u16 = AW9523_PIN_10;
170/// Port 1, pin 3 alias for PIN_11.
171/// This is the fourth pin in port 1, useful when organizing pins by port groups.
172pub const AW9523_P1_3: u16 = AW9523_PIN_11;
173/// Port 1, pin 4 alias for PIN_12.
174/// The middle pin of port 1, can be used with LED dimming functionality.
175pub const AW9523_P1_4: u16 = AW9523_PIN_12;
176/// Port 1, pin 5 alias for PIN_13.
177/// This pin is part of port 1's push-pull output group, accessible via upper-byte registers.
178pub const AW9523_P1_5: u16 = AW9523_PIN_13;
179/// Port 1, pin 6 alias for PIN_14.
180/// One of the higher-numbered pins in port 1, supporting all standard GPIO and LED modes.
181pub const AW9523_P1_6: u16 = AW9523_PIN_14;
182/// Port 1, pin 7 alias for PIN_15.
183/// The last pin on the AW9523, representing the highest bit in the 16-bit register space.
184pub const AW9523_P1_7: u16 = AW9523_PIN_15;
185
186/// Bitmask for all pins in port 0 (pins 0-7)
187pub const AW9523_PORT0_ALL: u16 = 0x00FF;
188/// Bitmask for all pins in port 1 (pins 8-15)
189pub const AW9523_PORT1_ALL: u16 = 0xFF00;
190/// Bitmask for all 16 pins
191pub const AW9523_ALL_PINS: u16 = 0xFFFF;
192
193/// Pin mode/direction configuration.
194///
195/// Defines how a pin should be configured when using [`Aw9523::pin_mode`].
196#[derive(Debug, Clone, Copy, PartialEq, Eq)]
197pub enum PinMode {
198    /// Configure pin as a digital input
199    Input,
200    /// Configure pin as a digital output
201    Output,
202    /// Configure pin as an LED driver with constant-current control
203    LedMode,
204}
205
206/// Pin mode constant for input direction (deprecated, use [`PinMode::Input`] instead)
207pub const INPUT: u8 = 0;
208/// Pin mode constant for output direction (deprecated, use [`PinMode::Output`] instead)
209pub const OUTPUT: u8 = 1;
210
211/// Constant for HIGH pin state
212pub const HIGH: PinState = PinState::High;
213/// Constant for LOW pin state
214pub const LOW: PinState = PinState::Low;
215
216/// Errors that can occur when interacting with the AW9523
217#[derive(Debug)]
218pub enum Aw9523Error<E> {
219    /// Operation not supported by the hardware
220    NotSupported(E),
221    /// Invalid argument passed to a method (e.g., pin number out of range)
222    InvalidArgument,
223    /// I2C read operation failed
224    ReadError(E),
225    /// I2C write operation failed
226    WriteError(E),
227}
228
229/// AW9523 driver instance
230///
231/// Manages communication with an AW9523 GPIO expander over I2C.
232/// The generic parameters allow flexibility in I2C implementation and addressing mode.
233pub struct Aw9523<A: AddressMode, I2C: I2c<A>> {
234    i2c: I2C,
235    addr: A,
236}
237
238impl<A, I2C> Aw9523<A, I2C>
239where
240    A: AddressMode + Copy,
241    I2C: I2c<A>,
242{
243    /// Creates a new AW9523 driver instance.
244    ///
245    /// # Arguments
246    ///
247    /// * `i2c` - An I2C bus implementation
248    /// * `addr` - The I2C address of the device (typically 0x58)
249    ///
250    /// # Example
251    ///
252    /// ```ignore
253    /// # use aw9523::Aw9523;
254    /// # let i2c = todo!();
255    /// let gpio = Aw9523::new(i2c, 0x58);
256    /// ```
257    pub fn new(i2c: I2C, addr: A) -> Self {
258        Self { i2c, addr }
259    }
260
261    /// Consumes the driver and returns the I2C bus.
262    ///
263    /// Useful for testing and when you need to reclaim the I2C peripheral.
264    #[cfg(test)]
265    pub fn destroy(self) -> I2C {
266        self.i2c
267    }
268
269    /// Initializes the AW9523 with a default configuration.
270    ///
271    /// Sets up output values, pin directions, and enables push-pull mode.
272    /// Call this after creating the device to ensure a known state.
273    ///
274    /// # Errors
275    ///
276    /// Returns an error if any I2C write operation fails.
277    pub fn init(&mut self) -> Result<(), Aw9523Error<I2C::Error>> {
278        self.write_register(&[AW9523_REG_OUTPUT0, 0b00000101])?;
279        self.write_register(&[AW9523_REG_OUTPUT1, 0b00000011])?;
280        self.write_register(&[AW9523_REG_CONFIG0, 0b00011000])?;
281        self.write_register(&[AW9523_REG_CONFIG1, 0b00001100])?;
282        self.write_register(&[AW9523_REG_GCR, 0b00010000])?;
283        self.write_register(&[AW9523_REG_LEDMODE1, 0b11111111])?;
284
285        Ok(())
286    }
287
288    /// Writes data to a register on the device.
289    fn write_register(&mut self, data: &[u8]) -> Result<(), Aw9523Error<I2C::Error>> {
290        self.i2c
291            .write(self.addr, data)
292            .map_err(Aw9523Error::WriteError)
293    }
294
295    /// Performs a soft reset of the device.
296    ///
297    /// All registers return to their default values after a reset.
298    /// You should call [`init`](Self::init) again after reset.
299    ///
300    /// # Errors
301    ///
302    /// Returns an error if the I2C write fails.
303    pub fn reset(&mut self) -> Result<(), Aw9523Error<I2C::Error>> {
304        self.write_register(&[AW9523_REG_SOFTRESET, 0x00])
305    }
306
307    /// Sets the output state for all 16 GPIO pins at once.
308    ///
309    /// Each bit in the 16-bit value represents one pin's state (1 = high, 0 = low).
310    /// Bit 0 corresponds to pin 0, bit 15 to pin 15.
311    ///
312    /// # Arguments
313    ///
314    /// * `pins` - 16-bit value where each bit sets the corresponding pin's output
315    ///
316    /// # Example
317    ///
318    /// ```ignore
319    /// # use aw9523::{Aw9523, AW9523_PIN_0, AW9523_PIN_3};
320    /// # let i2c = todo!();
321    /// # let mut gpio = Aw9523::new(i2c, 0x58);
322    /// // Set pins 0 and 3 high, all others low
323    /// gpio.output_gpio(AW9523_PIN_0 | AW9523_PIN_3).unwrap();
324    /// ```
325    pub fn output_gpio(&mut self, pins: u16) -> Result<(), Aw9523Error<I2C::Error>> {
326        self.write_register(&[AW9523_REG_OUTPUT0, (pins & 0xFF) as u8])?;
327        self.write_register(&[AW9523_REG_OUTPUT0 + 1, (pins >> 8) as u8])?;
328
329        Ok(())
330    }
331
332    /// Reads the input state of all 16 GPIO pins at once.
333    ///
334    /// Returns a 16-bit value where each bit represents one pin's state (1 = high, 0 = low).
335    /// Bit 0 corresponds to pin 0, bit 15 to pin 15.
336    ///
337    /// # Example
338    ///
339    /// ```ignore
340    /// # use aw9523::{Aw9523, AW9523_PIN_5};
341    /// # let i2c = todo!();
342    /// # let mut gpio = Aw9523::new(i2c, 0x58);
343    /// let inputs = gpio.input_gpio().unwrap();
344    /// if inputs & AW9523_PIN_5 != 0 {
345    ///     // Pin 5 is high
346    /// }
347    /// ```
348    pub fn input_gpio(&mut self) -> Result<u16, Aw9523Error<I2C::Error>> {
349        let mut buffer = [0u8; 2];
350        self.i2c
351            .write_read(self.addr, &[AW9523_REG_INPUT0], &mut buffer)
352            .map_err(Aw9523Error::ReadError)?;
353        Ok(u16::from(buffer[0]) | (u16::from(buffer[1]) << 8))
354    }
355
356    /// Enables interrupt detection for all 16 GPIO pins at once.
357    ///
358    /// Each bit in the 16-bit value enables (1) or disables (0) interrupt detection
359    /// for the corresponding pin.
360    ///
361    /// # Arguments
362    ///
363    /// * `pins` - 16-bit value where each bit enables the corresponding pin's interrupt
364    ///
365    /// # Note
366    ///
367    /// The AW9523 uses inverted logic internally, but this is handled automatically.
368    pub fn interrupt_enable_gpio(&mut self, pins: u16) -> Result<(), Aw9523Error<I2C::Error>> {
369        self.write_register(&[AW9523_REG_INTENABLE0, !(pins & 0xFF) as u8])?;
370        self.write_register(&[AW9523_REG_INTENABLE0 + 1, !(pins >> 8) as u8])?;
371
372        Ok(())
373    }
374
375    /// Configures pin direction for all 16 GPIO pins at once.
376    ///
377    /// Each bit in the 16-bit value sets the direction for the corresponding pin:
378    /// 1 = output, 0 = input.
379    ///
380    /// # Arguments
381    ///
382    /// * `pins` - 16-bit value where each bit sets the corresponding pin as output (1) or input (0)
383    ///
384    /// # Note
385    ///
386    /// The AW9523 uses inverted logic internally, but this is handled automatically.
387    pub fn configure_direction(&mut self, pins: u16) -> Result<(), Aw9523Error<I2C::Error>> {
388        self.write_register(&[AW9523_REG_CONFIG0, !(pins & 0xFF) as u8])?;
389        self.write_register(&[AW9523_REG_CONFIG0 + 1, !(pins >> 8) as u8])?;
390
391        Ok(())
392    }
393
394    /// Configures LED/constant-current mode for all 16 pins at once.
395    ///
396    /// Each bit in the 16-bit value enables (1) or disables (0) LED mode
397    /// for the corresponding pin. When enabled, the pin can be used with
398    /// [`analog_write`](Self::analog_write) for LED dimming.
399    ///
400    /// # Arguments
401    ///
402    /// * `pins` - 16-bit value where each bit enables LED mode for the corresponding pin
403    ///
404    /// # Note
405    ///
406    /// The AW9523 uses inverted logic internally, but this is handled automatically.
407    pub fn configure_led_mode(&mut self, pins: u16) -> Result<(), Aw9523Error<I2C::Error>> {
408        self.write_register(&[AW9523_REG_LEDMODE, !(pins & 0xFF) as u8])?;
409        self.write_register(&[AW9523_REG_LEDMODE + 1, !(pins >> 8) as u8])?;
410
411        Ok(())
412    }
413
414    /// Sets the LED brightness/current level for a single pin.
415    ///
416    /// The pin must be configured in LED mode first using [`pin_mode`](Self::pin_mode)
417    /// or [`configure_led_mode`](Self::configure_led_mode).
418    ///
419    /// # Arguments
420    ///
421    /// * `pin` - Pin number (0-15)
422    /// * `val` - Brightness level (0 = off, 255 = maximum current)
423    ///
424    /// # Errors
425    ///
426    /// Returns `InvalidArgument` if the pin number is greater than 15.
427    ///
428    /// # Example
429    ///
430    /// ```ignore
431    /// # use aw9523::{Aw9523, PinMode};
432    /// # let i2c = todo!();
433    /// # let mut gpio = Aw9523::new(i2c, 0x58);
434    /// gpio.pin_mode(0, PinMode::LedMode).unwrap();
435    /// gpio.analog_write(0, 128).unwrap(); // 50% brightness
436    /// ```
437    pub fn analog_write(&mut self, pin: u8, val: u8) -> Result<(), Aw9523Error<I2C::Error>> {
438        let reg = match pin {
439            0..=7 => 0x24 + pin,
440            8..=11 => 0x20 + pin - 8,
441            12..=15 => 0x2C + pin - 12,
442            _ => return Err(Aw9523Error::InvalidArgument),
443        };
444
445        self.write_register(&[reg, val])
446    }
447
448    /// Sets the digital output state for a single pin.
449    ///
450    /// The pin must be configured as an output first using [`pin_mode`](Self::pin_mode)
451    /// or [`configure_direction`](Self::configure_direction).
452    ///
453    /// # Arguments
454    ///
455    /// * `pin` - Pin number (0-15)
456    /// * `state` - Output state ([`HIGH`] / [`LOW`] or [`PinState::High`] / [`PinState::Low`])
457    ///
458    /// # Errors
459    ///
460    /// Returns `InvalidArgument` if the pin number is greater than 15.
461    ///
462    /// # Example
463    ///
464    /// ```ignore
465    /// # use aw9523::{Aw9523, PinMode, HIGH, LOW};
466    /// # let i2c = todo!();
467    /// # let mut gpio = Aw9523::new(i2c, 0x58);
468    /// gpio.pin_mode(3, PinMode::Output).unwrap();
469    /// gpio.digital_write(3, HIGH).unwrap(); // Set pin 3 high
470    /// gpio.digital_write(3, LOW).unwrap(); // Set pin 3 low
471    /// ```
472    pub fn digital_write(
473        &mut self,
474        pin: u8,
475        state: PinState,
476    ) -> Result<(), Aw9523Error<I2C::Error>> {
477        if pin > 15 {
478            return Err(Aw9523Error::InvalidArgument);
479        }
480
481        let reg_addr = AW9523_REG_OUTPUT0 + (pin / 8);
482        let bit_pos = pin % 8;
483
484        let mut buffer = [0u8; 1];
485        self.i2c
486            .write_read(self.addr, &[reg_addr], &mut buffer)
487            .map_err(Aw9523Error::ReadError)?;
488
489        let new_val = match state {
490            PinState::High => buffer[0] | (1 << bit_pos),
491            PinState::Low => buffer[0] & !(1 << bit_pos),
492        };
493        self.write_register(&[reg_addr, new_val])
494    }
495
496    /// Reads the digital input state for a single pin.
497    ///
498    /// The pin must be configured as an input first using [`pin_mode`](Self::pin_mode)
499    /// or [`configure_direction`](Self::configure_direction).
500    ///
501    /// # Arguments
502    ///
503    /// * `pin` - Pin number (0-15)
504    ///
505    /// # Returns
506    ///
507    /// Returns `true` if the pin is high, `false` if low.
508    ///
509    /// # Errors
510    ///
511    /// Returns `InvalidArgument` if the pin number is greater than 15.
512    ///
513    /// # Example
514    ///
515    /// ```ignore
516    /// # use aw9523::{Aw9523, PinMode};
517    /// # let i2c = todo!();
518    /// # let mut gpio = Aw9523::new(i2c, 0x58);
519    /// gpio.pin_mode(8, PinMode::Input).unwrap();
520    /// let state = gpio.digital_read(8).unwrap();
521    /// ```
522    pub fn digital_read(&mut self, pin: u8) -> Result<bool, Aw9523Error<I2C::Error>> {
523        if pin > 15 {
524            return Err(Aw9523Error::InvalidArgument);
525        }
526
527        let reg_addr = AW9523_REG_INPUT0 + (pin / 8);
528        let bit_pos = pin % 8;
529
530        let mut buffer = [0u8; 1];
531        self.i2c
532            .write_read(self.addr, &[reg_addr], &mut buffer)
533            .map_err(Aw9523Error::ReadError)?;
534        Ok((buffer[0] & (1 << bit_pos)) != 0)
535    }
536
537    /// Enables or disables interrupt detection for a single pin.
538    ///
539    /// # Arguments
540    ///
541    /// * `pin` - Pin number (0-15)
542    /// * `en` - true to enable interrupt detection, false to disable
543    ///
544    /// # Errors
545    ///
546    /// Returns `InvalidArgument` if the pin number is greater than 15.
547    ///
548    /// # Note
549    ///
550    /// The AW9523 uses inverted logic internally, but this is handled automatically.
551    pub fn enable_interrupt(&mut self, pin: u8, en: bool) -> Result<(), Aw9523Error<I2C::Error>> {
552        if pin > 15 {
553            return Err(Aw9523Error::InvalidArgument);
554        }
555
556        let reg_addr = AW9523_REG_INTENABLE0 + (pin / 8);
557        let bit_pos = pin % 8;
558
559        let mut buffer = [0u8; 1];
560        self.i2c
561            .write_read(self.addr, &[reg_addr], &mut buffer)
562            .map_err(Aw9523Error::ReadError)?;
563
564        let new_val = if en {
565            buffer[0] & !(1 << bit_pos)
566        } else {
567            buffer[0] | (1 << bit_pos)
568        };
569        self.write_register(&[reg_addr, new_val])
570    }
571
572    /// Configures the mode for a single pin.
573    ///
574    /// This is the main method for setting up pins. It configures both the direction
575    /// (input/output) and whether the pin operates as a GPIO or LED driver.
576    ///
577    /// # Arguments
578    ///
579    /// * `pin` - Pin number (0-15)
580    /// * `mode` - Pin configuration mode:
581    ///   - [`PinMode::Input`] - Digital input
582    ///   - [`PinMode::Output`] - Digital output
583    ///   - [`PinMode::LedMode`] - LED driver with current control
584    ///
585    /// # Errors
586    ///
587    /// Returns `InvalidArgument` if the pin number is greater than 15.
588    ///
589    /// # Example
590    ///
591    /// ```ignore
592    /// # use aw9523::{Aw9523, PinMode};
593    /// # let i2c = todo!();
594    /// # let mut gpio = Aw9523::new(i2c, 0x58);
595    /// gpio.pin_mode(0, PinMode::Output).unwrap();  // Digital output
596    /// gpio.pin_mode(5, PinMode::Input).unwrap();   // Digital input
597    /// gpio.pin_mode(12, PinMode::LedMode).unwrap(); // LED driver
598    /// ```
599    pub fn pin_mode(&mut self, pin: u8, mode: PinMode) -> Result<(), Aw9523Error<I2C::Error>> {
600        if pin > 15 {
601            return Err(Aw9523Error::InvalidArgument);
602        }
603
604        let bit_pos = pin % 8;
605
606        let config_reg = AW9523_REG_CONFIG0 + (pin / 8);
607        let mut config_buffer = [0u8; 1];
608        self.i2c
609            .write_read(self.addr, &[config_reg], &mut config_buffer)
610            .map_err(Aw9523Error::ReadError)?;
611
612        let ledmode_reg = AW9523_REG_LEDMODE + (pin / 8);
613        let mut ledmode_buffer = [0u8; 1];
614        self.i2c
615            .write_read(self.addr, &[ledmode_reg], &mut ledmode_buffer)
616            .map_err(Aw9523Error::ReadError)?;
617
618        let (config_val, ledmode_val) = match mode {
619            PinMode::Output => {
620                let conf = config_buffer[0] & !(1 << bit_pos);
621                let led = ledmode_buffer[0] | (1 << bit_pos);
622                (conf, led)
623            }
624            PinMode::Input => {
625                let conf = config_buffer[0] | (1 << bit_pos);
626                let led = ledmode_buffer[0] | (1 << bit_pos);
627                (conf, led)
628            }
629            PinMode::LedMode => {
630                let conf = config_buffer[0] & !(1 << bit_pos);
631                let led = ledmode_buffer[0] & !(1 << bit_pos);
632                (conf, led)
633            }
634        };
635        self.write_register(&[config_reg, config_val])?;
636        self.write_register(&[ledmode_reg, ledmode_val])?;
637
638        Ok(())
639    }
640
641    /// Configures output mode for all port 0 pins (pins 0-7).
642    ///
643    /// Sets whether port 0 pins use open-drain or push-pull output mode.
644    /// This affects all 8 pins in port 0 simultaneously.
645    ///
646    /// # Arguments
647    ///
648    /// * `od` - true for open-drain mode, false for push-pull mode
649    ///
650    /// # Note
651    ///
652    /// Port 1 (pins 8-15) always operates in push-pull mode and cannot be changed.
653    /// The AW9523 uses inverted logic internally, but this is handled automatically.
654    pub fn open_drain_port0(&mut self, od: bool) -> Result<(), Aw9523Error<I2C::Error>> {
655        let mut buffer = [0u8; 1];
656        self.i2c
657            .write_read(self.addr, &[AW9523_REG_GCR], &mut buffer)
658            .map_err(Aw9523Error::ReadError)?;
659
660        let new_val = if od {
661            buffer[0] & !(1 << 4)
662        } else {
663            buffer[0] | (1 << 4)
664        };
665        self.write_register(&[AW9523_REG_GCR, new_val])
666    }
667}
668
669#[cfg(test)]
670mod tests {
671    use super::*;
672    use embedded_hal_mock::eh1::i2c::{Mock as I2cMock, Transaction as I2cTransaction};
673
674    const ADDR: u8 = 0x58;
675
676    /// Helper macro to create a write transaction with register address and value
677    macro_rules! write_reg {
678        ($addr:expr, $reg:expr, $val:expr) => {
679            I2cTransaction::write($addr, [$reg, $val].to_vec())
680        };
681    }
682
683    #[test]
684    fn init_writes_expected_registers_in_order() {
685        // init() writes 6 registers
686        let expectations = [
687            write_reg!(ADDR, AW9523_REG_OUTPUT0, 0b0000_0101),
688            write_reg!(ADDR, AW9523_REG_OUTPUT1, 0b0000_0011),
689            write_reg!(ADDR, AW9523_REG_CONFIG0, 0b0001_1000),
690            write_reg!(ADDR, AW9523_REG_CONFIG1, 0b0000_1100),
691            write_reg!(ADDR, AW9523_REG_GCR, 0b0001_0000),
692            write_reg!(ADDR, AW9523_REG_LEDMODE1, 0b1111_1111),
693        ];
694
695        let i2c = I2cMock::new(&expectations);
696        let mut dev = Aw9523::new(i2c, ADDR);
697
698        dev.init().unwrap();
699
700        let mut i2c = dev.destroy();
701        i2c.done();
702    }
703
704    #[test]
705    fn reset_writes_softreset_register() {
706        let expectations = [write_reg!(ADDR, AW9523_REG_SOFTRESET, 0x00)];
707
708        let i2c = I2cMock::new(&expectations);
709        let mut dev = Aw9523::new(i2c, ADDR);
710
711        dev.reset().unwrap();
712
713        let mut i2c = dev.destroy();
714        i2c.done();
715    }
716
717    #[test]
718    fn output_gpio_writes_both_ports() {
719        // output_gpio(u16) writes low byte to OUTPUT0 (0x02) and high byte to OUTPUT1 (0x03)
720        let pins: u16 = 0xA55A; // low=0x5A, high=0xA5
721        let expectations = [
722            write_reg!(ADDR, AW9523_REG_OUTPUT0, 0x5A),
723            write_reg!(ADDR, AW9523_REG_OUTPUT0 + 1, 0xA5),
724        ];
725
726        let i2c = I2cMock::new(&expectations);
727        let mut dev = Aw9523::new(i2c, ADDR);
728
729        dev.output_gpio(pins).unwrap();
730
731        let mut i2c = dev.destroy();
732        i2c.done();
733    }
734
735    #[test]
736    fn input_gpio_reads_two_bytes_and_combines() {
737        // input_gpio() performs one write_read starting at INPUT0 (0x00) and reads two bytes.
738        // The driver combines them into a u16: low=buf[0], high=buf[1].
739        let expectations = [I2cTransaction::write_read(
740            ADDR,
741            [AW9523_REG_INPUT0].to_vec(),
742            [0x34, 0x12].to_vec(),
743        )];
744
745        let i2c = I2cMock::new(&expectations);
746        let mut dev = Aw9523::new(i2c, ADDR);
747
748        let v = dev.input_gpio().unwrap();
749        assert_eq!(v, 0x1234);
750
751        let mut i2c = dev.destroy();
752        i2c.done();
753    }
754
755    #[test]
756    fn interrupt_enable_gpio_writes_inverted_masks() {
757        // interrupt_enable_gpio(pins): datasheet uses inverted logic in the enable registers.
758        // The code writes bitwise NOT of each byte.
759        let pins: u16 = 0x00F0; // low=0xF0, high=0x00
760        let expectations = [
761            write_reg!(ADDR, AW9523_REG_INTENABLE0, !0xF0),
762            write_reg!(ADDR, AW9523_REG_INTENABLE0 + 1, !0x00),
763        ];
764
765        let i2c = I2cMock::new(&expectations);
766        let mut dev = Aw9523::new(i2c, ADDR);
767
768        dev.interrupt_enable_gpio(pins).unwrap();
769
770        let mut i2c = dev.destroy();
771        i2c.done();
772    }
773
774    #[test]
775    fn configure_direction_writes_inverted_masks() {
776        // configure_direction(pins): API uses 1=output, 0=input, but hardware config is 1=input.
777        // The code inverts the output-bitmask before writing to CONFIG regs.
778        let pins: u16 = 0x0F0F; // request these as outputs
779        let expectations = [
780            write_reg!(ADDR, AW9523_REG_CONFIG0, !(pins as u8)),
781            write_reg!(ADDR, AW9523_REG_CONFIG0 + 1, !((pins >> 8) as u8)),
782        ];
783
784        let i2c = I2cMock::new(&expectations);
785        let mut dev = Aw9523::new(i2c, ADDR);
786
787        dev.configure_direction(pins).unwrap();
788
789        let mut i2c = dev.destroy();
790        i2c.done();
791    }
792
793    #[test]
794    fn configure_led_mode_writes_inverted_masks() {
795        // configure_led_mode(pins): API uses 1=enable LED (constant current),
796        // but register uses 0=LED mode, 1=GPIO mode. So it inverts before writing.
797        let pins: u16 = 0x00FF; // enable LED mode on P0 pins only
798        let expectations = [
799            write_reg!(ADDR, AW9523_REG_LEDMODE, !(pins as u8)),
800            write_reg!(ADDR, AW9523_REG_LEDMODE + 1, !((pins >> 8) as u8)),
801        ];
802
803        let i2c = I2cMock::new(&expectations);
804        let mut dev = Aw9523::new(i2c, ADDR);
805
806        dev.configure_led_mode(pins).unwrap();
807
808        let mut i2c = dev.destroy();
809        i2c.done();
810    }
811
812    #[test]
813    fn analog_write_pin_register_mapping_examples() {
814        // analog_write() maps a pin number to a DIM register address.
815        // This test checks a few representative pins across the mapping ranges.
816        let expectations = [
817            // pin 0 => 0x24
818            write_reg!(ADDR, 0x24, 10),
819            // pin 7 => 0x2B
820            write_reg!(ADDR, 0x2B, 20),
821            // pin 8 => 0x20
822            write_reg!(ADDR, 0x20, 30),
823            // pin 11 => 0x23
824            write_reg!(ADDR, 0x23, 40),
825            // pin 12 => 0x2C
826            write_reg!(ADDR, 0x2C, 50),
827            // pin 15 => 0x2F
828            write_reg!(ADDR, 0x2F, 60),
829        ];
830
831        let i2c = I2cMock::new(&expectations);
832        let mut dev = Aw9523::new(i2c, ADDR);
833
834        dev.analog_write(0, 10).unwrap();
835        dev.analog_write(7, 20).unwrap();
836        dev.analog_write(8, 30).unwrap();
837        dev.analog_write(11, 40).unwrap();
838        dev.analog_write(12, 50).unwrap();
839        dev.analog_write(15, 60).unwrap();
840
841        let mut i2c = dev.destroy();
842        i2c.done();
843    }
844
845    #[test]
846    fn analog_write_rejects_invalid_pin() {
847        let i2c = I2cMock::new(&[]);
848        let mut dev = Aw9523::new(i2c, ADDR);
849
850        let err = dev.analog_write(16, 1).unwrap_err();
851        matches!(err, Aw9523Error::InvalidArgument);
852
853        let mut i2c = dev.destroy();
854        i2c.done();
855    }
856
857    #[test]
858    fn digital_write_sets_bit_in_output0() {
859        // digital_write():
860        // 1) read output register for the port (write_read)
861        // 2) set/clear the bit
862        // 3) write back modified byte
863        //
864        // Example: pin 3 is in OUTPUT0 (0x02), bit 3.
865        let expectations = [
866            I2cTransaction::write_read(ADDR, [AW9523_REG_OUTPUT0].to_vec(), [0b0000_0001].to_vec()),
867            write_reg!(ADDR, AW9523_REG_OUTPUT0, 0b0000_1001),
868        ];
869
870        let i2c = I2cMock::new(&expectations);
871        let mut dev = Aw9523::new(i2c, ADDR);
872
873        dev.digital_write(3, PinState::High).unwrap();
874
875        let mut i2c = dev.destroy();
876        i2c.done();
877    }
878
879    #[test]
880    fn digital_write_clears_bit_in_output1() {
881        // Example: pin 12 is in OUTPUT1 (0x03), bit (12%8)=4.
882        let reg = AW9523_REG_OUTPUT0 + 1;
883        let expectations = [
884            I2cTransaction::write_read(ADDR, [reg].to_vec(), [0b1111_1111].to_vec()),
885            write_reg!(ADDR, reg, 0b1110_1111),
886        ];
887
888        let i2c = I2cMock::new(&expectations);
889        let mut dev = Aw9523::new(i2c, ADDR);
890
891        dev.digital_write(12, PinState::Low).unwrap();
892
893        let mut i2c = dev.destroy();
894        i2c.done();
895    }
896
897    #[test]
898    fn digital_write_rejects_invalid_pin() {
899        let i2c = I2cMock::new(&[]);
900        let mut dev = Aw9523::new(i2c, ADDR);
901
902        let err = dev.digital_write(99, PinState::High).unwrap_err();
903        matches!(err, Aw9523Error::InvalidArgument);
904
905        let mut i2c = dev.destroy();
906        i2c.done();
907    }
908
909    #[test]
910    fn digital_read_reads_correct_port_and_bit() {
911        // Example: pin 9 is INPUT1 (0x01), bit 1.
912        let reg = AW9523_REG_INPUT0 + 1;
913        let expectations = [I2cTransaction::write_read(
914            ADDR,
915            [reg].to_vec(),
916            [0b0000_0010].to_vec(),
917        )];
918
919        let i2c = I2cMock::new(&expectations);
920        let mut dev = Aw9523::new(i2c, ADDR);
921
922        let v = dev.digital_read(9).unwrap();
923        assert!(v);
924
925        let mut i2c = dev.destroy();
926        i2c.done();
927    }
928
929    #[test]
930    fn enable_interrupt_enables_by_clearing_bit() {
931        // enable_interrupt(pin, true) clears the bit (0 = enabled).
932        // Example: pin 2 => INTENABLE0 (0x06), bit 2.
933        let reg = AW9523_REG_INTENABLE0;
934        let expectations = [
935            I2cTransaction::write_read(ADDR, [reg].to_vec(), [0b1111_1111].to_vec()),
936            write_reg!(ADDR, reg, 0b1111_1011),
937        ];
938
939        let i2c = I2cMock::new(&expectations);
940        let mut dev = Aw9523::new(i2c, ADDR);
941
942        dev.enable_interrupt(2, true).unwrap();
943
944        let mut i2c = dev.destroy();
945        i2c.done();
946    }
947
948    #[test]
949    fn enable_interrupt_disables_by_setting_bit() {
950        // enable_interrupt(pin, false) sets the bit (1 = disabled).
951        // Example: pin 10 => INTENABLE1 (0x07), bit 2.
952        let reg = AW9523_REG_INTENABLE0 + 1;
953        let expectations = [
954            I2cTransaction::write_read(ADDR, [reg].to_vec(), [0b0000_0000].to_vec()),
955            write_reg!(ADDR, reg, 0b0000_0100),
956        ];
957
958        let i2c = I2cMock::new(&expectations);
959        let mut dev = Aw9523::new(i2c, ADDR);
960
961        dev.enable_interrupt(10, false).unwrap();
962
963        let mut i2c = dev.destroy();
964        i2c.done();
965    }
966
967    #[test]
968    fn pin_mode_output_sets_config0_bit_to_0_and_ledmode_to_gpio() {
969        // pin_mode(pin, PinMode::Output):
970        // - CONFIG bit cleared (0=output)
971        // - LEDMODE bit set (1=GPIO mode)
972        //
973        // Example pin 5 => CONFIG0 (0x04) bit5, LEDMODE0 (0x12) bit5.
974        let config_reg = AW9523_REG_CONFIG0;
975        let led_reg = AW9523_REG_LEDMODE;
976
977        let expectations = [
978            // reads
979            I2cTransaction::write_read(ADDR, [config_reg].to_vec(), [0b1111_1111].to_vec()),
980            I2cTransaction::write_read(ADDR, [led_reg].to_vec(), [0b0000_0000].to_vec()),
981            // writes
982            write_reg!(ADDR, config_reg, 0b1101_1111), // clear bit 5
983            write_reg!(ADDR, led_reg, 0b0010_0000),    // set bit 5
984        ];
985
986        let i2c = I2cMock::new(&expectations);
987        let mut dev = Aw9523::new(i2c, ADDR);
988
989        dev.pin_mode(5, PinMode::Output).unwrap();
990
991        let mut i2c = dev.destroy();
992        i2c.done();
993    }
994
995    #[test]
996    fn pin_mode_input_sets_config_bit_to_1_and_ledmode_to_gpio() {
997        // pin_mode(pin, PinMode::Input):
998        // - CONFIG bit set (1=input)
999        // - LEDMODE bit set (1=GPIO mode)
1000        //
1001        // Example pin 6 => CONFIG0 bit6, LEDMODE0 bit6.
1002        let config_reg = AW9523_REG_CONFIG0;
1003        let led_reg = AW9523_REG_LEDMODE;
1004
1005        let expectations = [
1006            I2cTransaction::write_read(ADDR, [config_reg].to_vec(), [0b0000_0000].to_vec()),
1007            I2cTransaction::write_read(ADDR, [led_reg].to_vec(), [0b0000_0000].to_vec()),
1008            write_reg!(ADDR, config_reg, 0b0100_0000), // set bit 6
1009            write_reg!(ADDR, led_reg, 0b0100_0000),    // set bit 6
1010        ];
1011
1012        let i2c = I2cMock::new(&expectations);
1013        let mut dev = Aw9523::new(i2c, ADDR);
1014
1015        dev.pin_mode(6, PinMode::Input).unwrap();
1016
1017        let mut i2c = dev.destroy();
1018        i2c.done();
1019    }
1020
1021    #[test]
1022    fn pin_mode_led_mode_sets_output_and_led_mode_bits() {
1023        // pin_mode(pin, PinMode::LedMode):
1024        // - CONFIG bit cleared (output)
1025        // - LEDMODE bit cleared (0=LED mode)
1026        //
1027        // Example pin 14 => CONFIG1 (0x05) bit6, LEDMODE1 (0x13) bit6.
1028        let config_reg = AW9523_REG_CONFIG0 + 1;
1029        let led_reg = AW9523_REG_LEDMODE + 1;
1030
1031        let expectations = [
1032            I2cTransaction::write_read(ADDR, [config_reg].to_vec(), [0b1111_1111].to_vec()),
1033            I2cTransaction::write_read(ADDR, [led_reg].to_vec(), [0b1111_1111].to_vec()),
1034            write_reg!(ADDR, config_reg, 0b1011_1111), // clear bit 6
1035            write_reg!(ADDR, led_reg, 0b1011_1111),    // clear bit 6
1036        ];
1037
1038        let i2c = I2cMock::new(&expectations);
1039        let mut dev = Aw9523::new(i2c, ADDR);
1040
1041        dev.pin_mode(14, PinMode::LedMode).unwrap();
1042
1043        let mut i2c = dev.destroy();
1044        i2c.done();
1045    }
1046
1047    #[test]
1048    fn open_drain_port0_true_clears_bit_4() {
1049        // open_drain_port0(true):
1050        // - reads GCR
1051        // - clears bit 4 to select open-drain
1052        // - writes back
1053        let expectations = [
1054            I2cTransaction::write_read(ADDR, [AW9523_REG_GCR].to_vec(), [0b0001_0000].to_vec()),
1055            write_reg!(ADDR, AW9523_REG_GCR, 0b0000_0000),
1056        ];
1057
1058        let i2c = I2cMock::new(&expectations);
1059        let mut dev = Aw9523::new(i2c, ADDR);
1060
1061        dev.open_drain_port0(true).unwrap();
1062
1063        let mut i2c = dev.destroy();
1064        i2c.done();
1065    }
1066
1067    #[test]
1068    fn open_drain_port0_false_sets_bit_4() {
1069        // open_drain_port0(false):
1070        // - sets bit 4 to select push-pull
1071        let expectations = [
1072            I2cTransaction::write_read(ADDR, [AW9523_REG_GCR].to_vec(), [0b0000_0000].to_vec()),
1073            write_reg!(ADDR, AW9523_REG_GCR, 0b0001_0000),
1074        ];
1075
1076        let i2c = I2cMock::new(&expectations);
1077        let mut dev = Aw9523::new(i2c, ADDR);
1078
1079        dev.open_drain_port0(false).unwrap();
1080
1081        let mut i2c = dev.destroy();
1082        i2c.done();
1083    }
1084}
1085
1086/// Async implementation of the AW9523 driver.
1087///
1088/// This module provides an async version of the AW9523 driver that uses
1089/// `embedded-hal-async` traits. Enable the `async` feature to use this module.
1090///
1091/// All methods in this module are asynchronous and must be `.await`ed.
1092///
1093/// # Example
1094///
1095/// ```ignore
1096/// use aw9523::{r#async::Aw9523Async, OUTPUT, HIGH};
1097/// # let i2c = todo!(); // async I2C
1098///
1099/// async fn configure_gpio() {
1100///     let mut gpio = Aw9523Async::new(i2c, 0x58);
1101///     gpio.init().await.unwrap();
1102///     gpio.pin_mode(0, OUTPUT).await.unwrap();
1103///     gpio.digital_write(0, HIGH).await.unwrap();
1104/// }
1105/// ```
1106#[cfg(feature = "async")]
1107#[path = "async_impl.rs"]
1108pub mod r#async;