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;