# 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).