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):

PiTLV320DAC3100
5.0VVIN
GNDGND
SCL1SCL
SDA1SDA
IO21 (PCM_DOUT)DIN
IO19 (PCM_FS)WSEL
IO18 (PCM_CLK)BCK
anyRST

[!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:

 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):

+dtoverlay=hifiberry-dac

Installation

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

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.

# 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:

# 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:

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, which served as a high-quality code reference. Their breakout board for the TLV320DAC3100 was used in development of this driver

License

This library is licensed under the MIT License.