Skip to main content

README.md

# TLV320DAC3100

An Elixir library to configure and control the TI TLV320DAC3100 Stereo DAC with
Headphone and Speaker Amplifiers on Nerves devices.

> [!NOTE]
> This driver currently only supports configuring the chip for 48kHz, 16-bit
> stereo audio output via I2S. Other audio formats may require tweaking of the
> initialization register values.
>
> If you are experiencing issues, read the contents of the file at
> `/proc/asound/card0/pcm0p/sub0/hw_params` (change indices for your sound card)
> while streaming audio to help debug.

## Hardware Connection

The following pinout connects a Raspberry Pi to the TLV320DAC3100 over I2C (for
control) and I2S (for audio data):

| Pi              | TLV320DAC3100 |
| --------------- | ------------- |
| 5.0V            | VIN           |
| GND             | GND           |
| SCL1            | SCL           |
| SDA1            | SDA           |
| IO21 (PCM_DOUT) | DIN           |
| IO19 (PCM_FS)   | WSEL          |
| IO18 (PCM_CLK)  | BCK           |
| any             | RST           |

> [!NOTE]
> The driver configures the chip to source MCLK internally from BCK via the PLL,
> so no MCLK connection to the Pi is needed.

The Raspberry Pi's I2S peripheral must also be enabled in `config.txt`:

```diff
 dtparam=i2c_arm=on
 dtparam=spi=on
+dtparam=i2s=on
```

Then at the end of the `config.txt`, add the overlay for the DAC (this just sets
up a generic, no-config I2S sound card):

```diff
+dtoverlay=hifiberry-dac
```

## Installation

The package can be installed by adding `tlv320dac3100` to your list of
dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:tlv320dac3100, "~> 0.1.0"}
  ]
end
```

## Quick Start Example

You can initialize and configure the TLV320DAC3100 chip by creating and
pipeline-configuring a `%TLV320DAC3100{}` struct.

```elixir
# Open the I2C bus (assuming "i2c-1")
{:ok, i2c} = Circuits.I2C.open("i2c-1")

# Create the driver with optional hardware safety limits (in dB)
dac = TLV320DAC3100.new(i2c, 
  reset_pin: "GPIO26",
  speaker_max_volume: -3.0,
  speaker_max_gain: 12,
  headphone_max_volume: -10.0,
  headphone_max_gain: 6
)

# Initialize the driver clocks and registers
dac = TLV320DAC3100.initialize!(dac)

# Enable speaker output at 50% volume (scales safely up to speaker_max_volume)
dac = 
  dac
  |> TLV320DAC3100.set_speaker_output!(true)
  |> TLV320DAC3100.set_speaker_volume!(50)
  |> TLV320DAC3100.set_dac_volume!(-6.0)

# Mute the speaker temporarily if needed
dac = TLV320DAC3100.set_speaker_mute!(dac, true)
```

## Speaker/Headphone Volume vs. Speaker/Headphone Gain

The TLV320DAC3100 chip features two separate stages for adjusting output sound
levels, for both the speaker and headphone outputs.

* Volume represents an analog attenuation stage preceding the output driver.
* Volume operates on a high-resolution sliding scale from 0.0 dB down to -78.3
  dB in fine increments, for both speaker and headphone outputs.
* Gain controls the analog amplification factor of the output driver itself.
* Speaker gain can be set only to discrete hardware-fixed steps of 6 dB, 12 dB,
  18 dB, or 24 dB.
* Headphone gain can be set only to discrete hardware-fixed steps from 0 dB up
  to 9 dB in 1 dB increments.

### Best Practices

* Setting the speaker gain as low as possible (such as 6 dB or 12 dB) is
  generally recommended because setting it too high will amplify the background
  thermal noise floor.
* Keeping the speaker gain at 6 dB or 12 dB prevents small speaker models from
  being overdriven or damaged by excessive output power.
* Making fine real-time sound adjustments is best done using speaker volume
  rather than speaker gain.

## Pragmatic Percentage Volume Control

The library supports a convenient percentage-based volume API that automatically
scales integer values from `0` to `100` into correct hardware registers.

* Volume percentage 0% will automatically mute the respective output driver.
* Volume percentages 1% to 100% scale dynamically across all possible
  combinations of gain and attenuation configured in the struct.
* The percentage scaling algorithm always prefers the lowest possible analog
  gain when ranges overlap to minimize the background noise floor.
* Low-level decibel volume setters are available via `set_speaker_volume_db/2`
  and `set_headphone_volume_db/2` if you need direct decibel control.

> [!NOTE]
> On the speakers I have tested this library with, setting the volume to a low
> value (less than 20%) is indeed very quiet. The driver initializes at the
> minimum volume (to prevent speaker popping). You will probably want to set a
> default volume in your application before playing any audio.

## Headset and Microphone Detection

The TLV320DAC3100 chip supports automatic jack insertion and microphone
detection.

To use this feature, first enable the detection circuit using the configuration
API:

```elixir
# Enable headset detection with a 128ms debounce time (option values are 0 to 7)
dac = TLV320DAC3100.configure_headset_detection!(dac, true, detect_debounce: 3)
```

Once enabled, you can read the status of the headphone jack at any time:

```elixir
case TLV320DAC3100.get_headset_status(dac) do
  {:ok, :none, dac} ->
    # No headphone or headset is connected
    dac

  {:ok, :headphone, dac} ->
    # A standard 3-pole headphone (no mic) is connected
    TLV320DAC3100.set_headphone_output!(dac, true)

  {:ok, :headset, dac} ->
    # A standard 4-pole headset (with mic) is connected
    TLV320DAC3100.set_headphone_output!(dac, true)
end
```

* Headset status returns a real-time parsed atom (`:none`, `:headphone`,
  `:headset`, or `:reserved`) representing the connected device type.
* Debouncing can be customized separately for insertion (`:detect_debounce`,
  0-7) to prevent contact bounce noise.

## Acknowledgements

Special thanks to Adafruit for the
[Adafruit CircuitPython TLV320 driver](https://github.com/adafruit/Adafruit_CircuitPython_TLV320),
which served as a high-quality code reference. Their
[breakout board for the TLV320DAC3100](https://www.adafruit.com/product/6309)
was used in development of this driver

## License

This library is licensed under the [MIT License](LICENSE).