Metadata-Version: 2.4
Name: ad5593r
Version: 0.3.0
Summary: Python Library to interface with Analog Devices Inc. AD5593R Configurable ADC/DACs with On-chip Reference, I²C Interface featuring eight I/O pins.
Author-email: Jonathan Oxer <jon@oxer.com.au>
Maintainer-email: Jonathan Oxer <jon@oxer.com.au>
License: MIT
Project-URL: bugs, https://github.com/SuperHouse/ad5593r/issues
Project-URL: homepage, https://github.com/SuperHouse/ad5593r
Classifier: Topic :: Software Development :: Embedded Systems
Classifier: Topic :: System :: Hardware :: Hardware Drivers
Classifier: License :: OSI Approved :: MIT License
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: smbus2>=0.4.0
Dynamic: license-file

# AD5593R Python Library

The Analog Devices AD5593R I2C expander has 8 I/O pins that can be configured 
as ADC, DAC, or GPIO. It's a very expensive part but it's also extremely 
flexible, making it ideal for specialist devices such as test jigs that must 
be reconfigured to suit different test requirements.

Note: When first powered up, all I/O pins default to 85K pull-downs until 
their mode is specifically set.

This library was created for use with the 
[AD5593R-based I/O module](https://github.com/SuperHouse/IOMOD) 
as part of the 
[Testomatic PCB test jig system](https://github.com/superhouse/testomatic).

This began as a Python port of the AD5593R C++ Arduino library by Rob Tillaart, 
but has since deviated significantly to bring it more in line with Python 
conventions. If you want to use the AD5593R with Arduino, check out Rob's 
excellent library at 
[github.com/RobTillaart/AD5593R](https://github.com/RobTillaart/AD5593R).


## I/O Voltage and VREF

The AD5593R supports dual power domains, with one voltage for control via I2C and 
the other for operation of the I/O pins. They can be the same, or they can be 
different such as using 3.3V I2C to an MCU, and 5V on I/O pins.

When using the DAC and ADC analog functions, the interaction between the VREF value 
and the I/O voltage will determine how the values that you read and write will 
map to actual voltages.

ADC and DAC modes are 12-bit, with full range values of 0-4095. Analog voltages 
are referenced against either an internal 2.5V voltage reference or an 
external precision voltage reference.

When using the internal VREF with the default 1x multiplier, the full DAC output 
range is limited 0-2.5V, ie: writing a value of 0 gives 0V output, and writing 
a value of 4095 gives 2.5V output. When the 2x multiplier is enabled, the values 
are mapped with 0 giving 0V output and 4095 giving 5V output. HOWEVER, the catch 
is that if you are running the I/O voltage domain at (say) 3.3V, the values will 
still map to the entire range but the part of the range above 3.3V will never be 
reached. If you write a value of 2703 you will receive the maximum 3.3V output, 
and any values above that will still only give 3.3V output. This example shows 
the voltage output with 3.3V supply voltage, 2.5V internal VREF, 2x multiplier, 
and output values of 0-4095:

![Scope capture showing voltage rising to 3.3V then capped](images/vref-capped.png)

The implication of this is if you want to run the AD5593R at 3.3V I/O and 
achieve full-scale DAC output, you need to either:

 1. Use an external 3.3V precision voltage reference, leave the multiplier at 1x, 
    and use the full range of values from 0-4095, or
 2. Use the internal 2.5V VREF, turn on the 2x VREF multiplier, and map your 
    output values to 0-2703.

Conversely, if you want to run the AD5593R at 5V I/O and achieve full-scale DAC 
output with the internal 2.5V VREF, you must turn on the 2x VREF multiplier. 
Otherwise your output will be capped at 2.5V.


## Installation

Install the required dependency:

```bash
pip install -r requirements.txt
```

Or install directly:

```bash
pip install smbus2
```

## Usage

### Basic Setup

```python
from AD5593R import AD5593R, AD5593R_LDAC_DIRECT

# Create instance with I2C address 0x10 (or 0x11)
# bus_number defaults to 1 (for Raspberry Pi)
ad = AD5593R(0x10, bus_number=1)

# Initialize and check connection
if not ad.begin():
    print("AD5593R not found!")
    exit(1)

print(f"Connected: {ad.is_connected()}")
print(f"Address: 0x{ad.get_address():02X}")
```

### Configure Pins

```python
# Set all pins to DAC mode
ad.set_dac_mode(0xFF)

# Or use string configuration: A=ADC, D=DAC, I=INPUT, O=OUTPUT, T=TRISTATE
ad.set_mode("AADDIIOT")  # Configure all 8 pins with specific functions
```

### Configure a Single Pin

```python
from ad5593r import PinMode

# Change just pin 3 to DAC mode, without needing to know (or affect) what
# every other pin is currently configured as
ad.pin_mode(3, PinMode.DAC)

# Also accepts the mode's string value directly
ad.pin_mode(1, "input")
```

### Reference Voltage

```python
# Use internal reference (2.5V)
ad.set_external_reference(False, 0)

# Use external reference (e.g., 3.3V)
ad.set_external_reference(True, 3.3)

# Configure ranges
ad.set_dac_range_2x(False)  # 1x Vref
ad.set_adc_range_2x(False)  # 1x Vref

# Set LDAC mode (must be called after set_external_reference)
ad.set_ldac_mode(AD5593R_LDAC_DIRECT)
```

### Digital I/O

```python
# Set pin 0 as output
ad.set_output_mode(0x01)

# Write HIGH to pin 0
ad.digital_write(0, 1)

# Write LOW to pin 0
ad.digital_write(0, 0)

# Write to all pins at once
ad.digital_write_all(0b10101010)

# Set pin 1 as input
ad.set_input_mode(0x02)

# Read pin 1
value = ad.digital_read(1)

# Read all input pins
all_pins = ad.digital_read_all()
```

### Analog I/O

```python
# Write to DAC (12-bit, 0-4095)
ad.analog_write(0, 2048)  # Half scale

# Read back DAC value
value = ad.read_dac(0)

# Read ADC (12-bit, 0-4095)
ad.set_adc_mode(0x01)  # Set pin 0 as ADC
adc_value = ad.analog_read(0)

# Read temperature
temperature = ad.read_temperature()
print(f"Temperature: {temperature:.2f}°C")
```

### Power Management

```python
# Power down entire device
ad.power_down()

# Wake up
ad.wake_up()

# Power down specific DAC
ad.power_down_dac(0)

# Wake up specific DAC
ad.wake_up_dac(0)
```

### Reset

```python
# Software reset (resets Vref to 2.5V and gain to 1x)
ad.reset()
```

### Cleanup

```python
# Close I2C bus when done
ad.close()
```

## Examples

See the `examples/` directory:

- `examples/example_test_dac.py`: write increasing values to all 8 pins in DAC mode, in a loop
- `examples/example_digital_output.py`: set pin 0 to digital output and drive it high, then low
- `examples/example_digital_output_passed_i2c.py`: the same digital output example, but with the I2C connection set up externally and passed in
- `examples/example_digital_input.py`: set pin 1 to digital input and read it repeatedly
- `examples/example_dac_output.py`: set pin 2 to DAC mode and output 50% of full scale
- `examples/example_adc_input.py`: set pin 3 to ADC mode and read it repeatedly

`example_digital_output.py` and `example_digital_output_passed_i2c.py` cover the same pin operations but show the two ways to set up the I2C connection, so you can compare them directly:

- **Let AD5593R manage it** (`bus_number=1`, the default) — the simplest option, used by every other example here. AD5593R lazily opens its own `smbus2.SMBus` on first use, and `close()` closes it. This is enough for the common case of a single AD5593R directly on the Raspberry Pi's I2C bus.
- **Set it up yourself and pass it in** (`i2c_bus=...`) — needed whenever something else has to control the connection: for example, several devices sharing one bus, or a device sitting behind an I2C multiplexer that needs a channel selected before each transaction. AD5593R only needs an object exposing `writeto(address, buffer)`/`readfrom(address, length)`; since you own the connection's lifecycle, AD5593R won't open or close it for you.

## Differences from Rob Tillaart's Arduino library

This library is directly derived from Rob's work, so his library is a great 
reference if you want to understand more about how the AD5593R works or 
see his examples. Most of the register-level API calls (`set_mode`, 
`set_output_mode`, `set_external_reference`, `power_down`, etc.) stay close 
to his naming. However, some things were changed to be more Python-ish, including:

- Method names use snake_case instead of camelCase (eg: `setMode` is now `set_mode`)
- Constructor takes `bus_number` parameter instead of `TwoWire*` pointer, or 
  accepts an already-configured `i2c_bus` transport (e.g. to work behind an 
  I2C multiplexer) instead of opening its own bus
- Added `close()` method to properly close I2C bus
- Added `get_last_error()` method to check error status
- As of v0.2.0: the single/whole-pin I/O methods no longer follow Rob's 
  `write1`/`read1`/`write8`/`read8`/`write_dac`/`read_adc` naming (a "how many 
  pins does this touch" numbering convention from the Arduino library). 
  They're now `digital_write`/`digital_read`/`digital_write_all`/
  `digital_read_all`/`analog_write`/`analog_read`. This was a deliberate, 
  breaking rename made while adoption was still small, to read more like 
  idiomatic Python and to match the driver interface used by consumers of 
  this library (e.g. IOMod's driver layer). If you were a super early adopter 
  of this library and this change bit you: oops, sorry, I should have thought
  it through more before the initial release :-(

## Error Codes

- `AD5593R_OK` (0x0000): Success
- `AD5593R_PIN_ERROR` (0xFF81): Invalid pin number
- `AD5593R_I2C_ERROR` (0xFF82): I2C communication error
- `AD5593R_LDAC_ERROR` (0xFF83): Invalid LDAC mode

## Notes

- Make sure I2C is enabled on your system! On Raspberry Pi this is done using 
  `sudo raspi-config` -> `Interface Options` -> `I2C`
- Uses `smbus2` for I2C communication
- Default I2C bus is 1 for Raspberry Pi. Modern Raspberry Pi models reserve bus 0 
  for Pi Hat EEPROMs that are read at startup. Use `bus_number=0` for older 
  Raspberry Pi models
- The device supports I2C addresses 0x10 and 0x11, which are set by the A0 pin on 
  the AD5593R

## License

Released under the MIT licence.
