From a65bcdc3d45ace7c4cfde6227bf4c54a86fc57e7 Mon Sep 17 00:00:00 2001 From: Liz Date: Wed, 12 Mar 2025 16:55:16 -0400 Subject: [PATCH 01/11] sine tone and initial library --- adafruit_tlv320.py | 696 +++++++++++++++++++++++++++++++++++- examples/tlv320_sinetone.py | 107 ++++++ 2 files changed, 794 insertions(+), 9 deletions(-) create mode 100644 examples/tlv320_sinetone.py diff --git a/adafruit_tlv320.py b/adafruit_tlv320.py index 11f4bee..75053c2 100644 --- a/adafruit_tlv320.py +++ b/adafruit_tlv320.py @@ -1,4 +1,3 @@ -# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries # SPDX-FileCopyrightText: Copyright (c) 2025 Liz Clark for Adafruit Industries # # SPDX-License-Identifier: MIT @@ -16,22 +15,701 @@ Implementation Notes **Hardware:** -.. todo:: Add links to any specific hardware product page(s), or category page(s). - Use unordered list & hyperlink rST inline format: "* `Link Text `_" +* `Link Text `_ **Software and Dependencies:** * Adafruit CircuitPython firmware for the supported boards: https://circuitpython.org/downloads -.. todo:: Uncomment or remove the Bus Device and/or the Register library dependencies - based on the library's use of either. - -# * Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice -# * Adafruit's Register library: https://github.com/adafruit/Adafruit_CircuitPython_Register +* Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice +* Adafruit's Register library: https://github.com/adafruit/Adafruit_CircuitPython_Register """ -# imports +import time +from micropython import const +from adafruit_bus_device.i2c_device import I2CDevice __version__ = "0.0.0+auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_TLV320.git" + +# Register addresses +REG_PAGE_SELECT = const(0x00) +REG_RESET = const(0x01) +REG_OT_FLAG = const(0x03) +REG_CLOCK_MUX1 = const(0x04) +REG_PLL_PROG_PR = const(0x05) +REG_PLL_PROG_J = const(0x06) +REG_PLL_PROG_D_MSB = const(0x07) +REG_PLL_PROG_D_LSB = const(0x08) +REG_NDAC = const(0x0B) +REG_MDAC = const(0x0C) +REG_DOSR_MSB = const(0x0D) +REG_DOSR_LSB = const(0x0E) +REG_CLKOUT_MUX = const(0x19) +REG_CLKOUT_M = const(0x1A) +REG_CODEC_IF_CTRL1 = const(0x1B) +REG_DATA_SLOT_OFFSET = const(0x1C) +REG_BCLK_N = const(0x1E) +REG_DAC_FLAG = const(0x25) +REG_DAC_FLAG2 = const(0x26) +REG_INT1_CTRL = const(0x30) +REG_INT2_CTRL = const(0x31) +REG_GPIO1_CTRL = const(0x33) +REG_DIN_CTRL = const(0x36) +REG_DAC_PRB = const(0x3C) +REG_DAC_DATAPATH = const(0x3F) +REG_DAC_VOL_CTRL = const(0x40) +REG_DAC_LVOL = const(0x41) +REG_DAC_RVOL = const(0x42) +REG_HEADSET_DETECT = const(0x43) + +# Page 1 registers +REG_HP_SPK_ERR_CTL = const(0x1E) +REG_HP_DRIVERS = const(0x1F) +REG_SPK_AMP = const(0x20) +REG_HP_POP = const(0x21) +REG_PGA_RAMP = const(0x22) +REG_OUT_ROUTING = const(0x23) +REG_HPL_VOL = const(0x24) +REG_HPR_VOL = const(0x25) +REG_SPK_VOL = const(0x26) +REG_HPL_DRIVER = const(0x28) +REG_HPR_DRIVER = const(0x29) +REG_SPK_DRIVER = const(0x2A) +REG_HP_DRIVER_CTRL = const(0x2C) + +# Default I2C address +I2C_ADDR_DEFAULT = const(0x18) + +# Data format for I2S interface +FORMAT_I2S = const(0b00) # I2S format +FORMAT_DSP = const(0b01) # DSP format +FORMAT_RJF = const(0b10) # Right justified format +FORMAT_LJF = const(0b11) # Left justified format + +# Data length for I2S interface +DATA_LEN_16 = const(0b00) # 16 bits +DATA_LEN_20 = const(0b01) # 20 bits +DATA_LEN_24 = const(0b10) # 24 bits +DATA_LEN_32 = const(0b11) # 32 bits + +# Clock source options for PLL_CLKIN +PLL_CLKIN_MCLK = const(0b00) # MCLK pin is the source +PLL_CLKIN_BCLK = const(0b01) # BCLK pin is the source +PLL_CLKIN_GPIO1 = const(0b10) # GPIO1 pin is the source +PLL_CLKIN_DIN = const(0b11) # DIN pin is the source + +# Clock source options for CODEC_CLKIN +CODEC_CLKIN_MCLK = const(0b00) # MCLK pin is the source +CODEC_CLKIN_BCLK = const(0b01) # BCLK pin is the source +CODEC_CLKIN_GPIO1 = const(0b10) # GPIO1 pin is the source +CODEC_CLKIN_PLL = const(0b11) # PLL_CLK pin is the source + +# GPIO1 pin mode options +GPIO1_DISABLED = const(0b0000) # GPIO1 disabled (input and output buffers powered down) +GPIO1_INPUT_MODE = const(0b0001) # Input mode (secondary BCLK/WCLK/DIN input or ClockGen) +GPIO1_GPI = const(0b0010) # General-purpose input +GPIO1_GPO = const(0b0011) # General-purpose output +GPIO1_CLKOUT = const(0b0100) # CLKOUT output +GPIO1_INT1 = const(0b0101) # INT1 output +GPIO1_INT2 = const(0b0110) # INT2 output +GPIO1_BCLK_OUT = const(0b1000) # Secondary BCLK output for codec interface +GPIO1_WCLK_OUT = const(0b1001) # Secondary WCLK output for codec interface + +# DAC channel data path options +DAC_PATH_OFF = const(0b00) # DAC data path off +DAC_PATH_NORMAL = const(0b01) # Normal path (L->L or R->R) +DAC_PATH_SWAPPED = const(0b10) # Swapped path (R->L or L->R) +DAC_PATH_MIXED = const(0b11) # Mixed L+R path + +# DAC volume control soft stepping options +VOLUME_STEP_1SAMPLE = const(0b00) # One step per sample +VOLUME_STEP_2SAMPLE = const(0b01) # One step per two samples +VOLUME_STEP_DISABLED = const(0b10) # Soft stepping disabled + +# DAC volume control configuration options +VOL_INDEPENDENT = const(0b00) # Left and right channels independent +VOL_LEFT_TO_RIGHT = const(0b01) # Left follows right volume +VOL_RIGHT_TO_LEFT = const(0b10) # Right follows left volume + +# DAC output routing options +DAC_ROUTE_NONE = const(0b00) # DAC not routed +DAC_ROUTE_MIXER = const(0b01) # DAC routed to mixer amplifier +DAC_ROUTE_HP = const(0b10) # DAC routed directly to HP driver + +# Speaker amplifier gain options +SPK_GAIN_6DB = const(0b00) # 6 dB gain +SPK_GAIN_12DB = const(0b01) # 12 dB gain +SPK_GAIN_18DB = const(0b10) # 18 dB gain +SPK_GAIN_24DB = const(0b11) # 24 dB gain + +# Headphone common mode voltage settings +HP_COMMON_1_35V = const(0b00) # Common-mode voltage 1.35V +HP_COMMON_1_50V = const(0b01) # Common-mode voltage 1.50V +HP_COMMON_1_65V = const(0b10) # Common-mode voltage 1.65V +HP_COMMON_1_80V = const(0b11) # Common-mode voltage 1.80V + +# Headset detection debounce time options +DEBOUNCE_16MS = const(0b000) # 16ms debounce (2ms clock) +DEBOUNCE_32MS = const(0b001) # 32ms debounce (4ms clock) +DEBOUNCE_64MS = const(0b010) # 64ms debounce (8ms clock) +DEBOUNCE_128MS = const(0b011) # 128ms debounce (16ms clock) +DEBOUNCE_256MS = const(0b100) # 256ms debounce (32ms clock) +DEBOUNCE_512MS = const(0b101) # 512ms debounce (64ms clock) + +# Button press debounce time options +BTN_DEBOUNCE_0MS = const(0b00) # No debounce +BTN_DEBOUNCE_8MS = const(0b01) # 8ms debounce (1ms clock) +BTN_DEBOUNCE_16MS = const(0b10) # 16ms debounce (2ms clock) +BTN_DEBOUNCE_32MS = const(0b11) # 32ms debounce (4ms clock) + + +class PagedRegisterBase: + """Base class for paged register access.""" + + def __init__(self, i2c_device, page): + """Initialize the paged register base. + + :param i2c_device: The I2C device + :param page: The register page number + """ + self._device = i2c_device + self._page = page + self._buffer = bytearray(2) + + def _write_register(self, register, value): + """Write a value to a register. + + :param register: The register address + :param value: The value to write + """ + # First set the page + self._set_page() + + # Then write to the register + self._buffer[0] = register + self._buffer[1] = value + with self._device as i2c: + i2c.write(self._buffer) + + def _read_register(self, register): + """Read a value from a register. + + :param register: The register address + :return: The register value + """ + # First set the page + self._set_page() + + # Then read the register + self._buffer[0] = register + with self._device as i2c: + i2c.write(self._buffer, end=1) + i2c.readinto(self._buffer, start=0, end=1) + return self._buffer[0] + + def _set_page(self): + """Set the current register page.""" + self._buffer[0] = REG_PAGE_SELECT + self._buffer[1] = self._page + with self._device as i2c: + i2c.write(self._buffer) + + def _get_bits(self, register, mask, shift): + """Read specific bits from a register. + + :param register: The register address + :param mask: The bit mask (after shifting) + :param shift: The bit position (0 = LSB) + :return: The extracted bits + """ + value = self._read_register(register) + return (value >> shift) & mask + + def _set_bits(self, register, mask, shift, value): + """Set specific bits in a register. + + :param register: The register address + :param mask: The bit mask (after shifting) + :param shift: The bit position (0 = LSB) + :param value: The value to set + :return: True if successful + """ + reg_value = self._read_register(register) + reg_value &= ~(mask << shift) # Clear the bits + reg_value |= (value & mask) << shift # Set the new bits + self._write_register(register, reg_value) + return True + + +class Page0Registers(PagedRegisterBase): + """Page 0 registers containing system configuration, clocking, etc.""" + + def __init__(self, i2c_device): + """Initialize Page 0 registers. + + :param i2c_device: The I2C device + """ + super().__init__(i2c_device, 0) + + def reset(self): + """Perform a software reset of the chip. + + :return: True if successful, False otherwise + """ + # Set reset bit + self._write_register(REG_RESET, 1) + + # Wait for reset to complete + time.sleep(0.01) # 10ms delay + + # Reset bit should be 0 after reset completes + return self._read_register(REG_RESET) == 0 + + def is_overtemperature(self): + """Check if the chip is in an over-temperature condition. + + :return: True if overtemp condition exists, False if temperature is OK + """ + # Bit 1 of register 3 is the overtemp flag (0 = overtemp, 1 = normal) + return not ((self._read_register(REG_OT_FLAG) >> 1) & 0x01) + + # Methods with property potential + + def get_pll_clock_input(self): + """Get the PLL clock input source.""" + return self._get_bits(REG_CLOCK_MUX1, 0x03, 2) + + def set_pll_clock_input(self, clkin): + """Set the PLL clock input source.""" + return self._set_bits(REG_CLOCK_MUX1, 0x03, 2, clkin) + + def get_codec_clock_input(self): + """Get the CODEC clock input source.""" + return self._get_bits(REG_CLOCK_MUX1, 0x03, 0) + + def set_codec_clock_input(self, clkin): + """Set the CODEC clock input source.""" + return self._set_bits(REG_CLOCK_MUX1, 0x03, 0, clkin) + + def get_pll_power(self): + """Get the PLL power state.""" + return bool(self._get_bits(REG_PLL_PROG_PR, 0x01, 7)) + + def set_pll_power(self, on): + """Set the PLL power state.""" + return self._set_bits(REG_PLL_PROG_PR, 0x01, 7, 1 if on else 0) + + # Other methods + + def set_pll_values(self, p, r, j, d): + """Set the PLL P, R, J, and D values. + + :param p: PLL P value (1-8) + :param r: PLL R value (1-16) + :param j: PLL J value (1-63) + :param d: PLL D value (0-9999) + :return: True if successful, False if failure + """ + # Validate all input ranges + if p < 1 or p > 8: + return False + if r < 1 or r > 16: + return False + if j < 1 or j > 63: + return False + if d > 9999: + return False + + # P & R register + p_val = p % 8 # P values wrap at 8 + r_val = r % 16 # R values wrap at 16 + + # Write P (bits 6:4) and R (bits 3:0) values + pr_value = (p_val << 4) | r_val + self._write_register(REG_PLL_PROG_PR, pr_value) + + # J register (bits 5:0) + self._write_register(REG_PLL_PROG_J, j & 0x3F) + + # D MSB & LSB registers (14 bits total) + self._write_register(REG_PLL_PROG_D_MSB, (d >> 8) & 0xFF) + self._write_register(REG_PLL_PROG_D_LSB, d & 0xFF) + + return True + + def set_ndac(self, enable, val): + """Set the NDAC value and enable/disable.""" + # Validate input range + if val < 1 or val > 128: + return False + + value = ((1 if enable else 0) << 7) | (val % 128) + self._write_register(REG_NDAC, value) + return True + + def set_mdac(self, enable, val): + """Set the MDAC value and enable/disable.""" + # Validate input range + if val < 1 or val > 128: + return False + + value = ((1 if enable else 0) << 7) | (val % 128) + self._write_register(REG_MDAC, value) + return True + + def set_codec_interface(self, format, data_len, bclk_out=False, wclk_out=False): + """Set the codec interface parameters.""" + value = (format & 0x03) << 6 + value |= (data_len & 0x03) << 4 + value |= (1 if bclk_out else 0) << 3 + value |= (1 if wclk_out else 0) << 2 + + self._write_register(REG_CODEC_IF_CTRL1, value) + return True + + def set_int1_source(self, headset_detect, button_press, dac_drc, + agc_noise, over_current, multiple_pulse): + """Configure the INT1 interrupt sources.""" + value = 0 + if headset_detect: value |= (1 << 7) + if button_press: value |= (1 << 6) + if dac_drc: value |= (1 << 5) + if over_current: value |= (1 << 3) + if agc_noise: value |= (1 << 2) + if multiple_pulse: value |= (1 << 0) + + self._write_register(REG_INT1_CTRL, value) + return True + + def set_gpio1_mode(self, mode): + """Set the GPIO1 pin mode.""" + return self._set_bits(REG_GPIO1_CTRL, 0x0F, 2, mode) + + def set_headset_detect(self, enable, detect_debounce=0, button_debounce=0): + """Configure headset detection settings.""" + value = (1 if enable else 0) << 7 + value |= (detect_debounce & 0x07) << 2 + value |= (button_debounce & 0x03) + + self._write_register(REG_HEADSET_DETECT, value) + return True + + def set_dac_data_path(self, left_dac_on, right_dac_on, + left_path=DAC_PATH_NORMAL, + right_path=DAC_PATH_NORMAL, + volume_step=VOLUME_STEP_1SAMPLE): + """Configure the DAC data path settings.""" + value = 0 + if left_dac_on: value |= (1 << 7) + if right_dac_on: value |= (1 << 6) + value |= (left_path & 0x03) << 4 + value |= (right_path & 0x03) << 2 + value |= (volume_step & 0x03) + + self._write_register(REG_DAC_DATAPATH, value) + return True + + def set_dac_volume_control(self, left_mute, right_mute, control=VOL_INDEPENDENT): + """Configure the DAC volume control settings.""" + value = 0 + if left_mute: value |= (1 << 3) + if right_mute: value |= (1 << 2) + value |= (control & 0x03) + + self._write_register(REG_DAC_VOL_CTRL, value) + return True + + def set_channel_volume(self, right_channel, db): + """Set DAC channel volume in dB.""" + # Constrain input to valid range + if db > 24.0: + db = 24.0 + if db < -63.5: + db = -63.5 + + reg_val = int(db * 2) + + # Check for reserved values + if reg_val == 0x80 or reg_val > 0x30: + return False + + if right_channel: + self._write_register(REG_DAC_RVOL, reg_val & 0xFF) + else: + self._write_register(REG_DAC_LVOL, reg_val & 0xFF) + + return True + + +class Page1Registers(PagedRegisterBase): + """Page 1 registers containing analog output settings, HP/SPK controls, etc.""" + + def __init__(self, i2c_device): + """Initialize Page 1 registers. + + :param i2c_device: The I2C device + """ + super().__init__(i2c_device, 1) + + def get_speaker_enabled(self): + """Check if speaker is enabled.""" + return bool(self._get_bits(REG_SPK_AMP, 0x01, 7)) + + def set_speaker_enabled(self, enable): + """Enable or disable the Class-D speaker amplifier.""" + return self._set_bits(REG_SPK_AMP, 0x01, 7, 1 if enable else 0) + + def configure_headphone_driver(self, left_powered, right_powered, + common=HP_COMMON_1_35V, power_down_on_scd=False): + """Configure headphone driver settings.""" + value = 0x04 # bit 2 must be 1 + if left_powered: value |= (1 << 7) + if right_powered: value |= (1 << 6) + value |= (common & 0x03) << 3 + if power_down_on_scd: value |= (1 << 1) + + self._write_register(REG_HP_DRIVERS, value) + return True + + def configure_analog_inputs(self, left_dac=DAC_ROUTE_NONE, right_dac=DAC_ROUTE_NONE, + left_ain1=False, left_ain2=False, right_ain2=False, + hpl_routed_to_hpr=False): + """Configure DAC and analog input routing.""" + value = 0 + value |= (left_dac & 0x03) << 6 + if left_ain1: value |= (1 << 5) + if left_ain2: value |= (1 << 4) + value |= (right_dac & 0x03) << 2 + if right_ain2: value |= (1 << 1) + if hpl_routed_to_hpr: value |= 1 + + self._write_register(REG_OUT_ROUTING, value) + return True + + def set_hpl_volume(self, route_enabled, gain=0x7F): + """Set HPL analog volume control.""" + if gain > 0x7F: + gain = 0x7F + + value = ((1 if route_enabled else 0) << 7) | (gain & 0x7F) + self._write_register(REG_HPL_VOL, value) + return True + + def set_hpr_volume(self, route_enabled, gain=0x7F): + """Set HPR analog volume control.""" + if gain > 0x7F: + gain = 0x7F + + value = ((1 if route_enabled else 0) << 7) | (gain & 0x7F) + self._write_register(REG_HPR_VOL, value) + return True + + def set_spk_volume(self, route_enabled, gain=0x7F): + """Set Speaker analog volume control.""" + if gain > 0x7F: + gain = 0x7F + + value = ((1 if route_enabled else 0) << 7) | (gain & 0x7F) + self._write_register(REG_SPK_VOL, value) + return True + + def configure_hpl_pga(self, gain_db=0, unmute=True): + """Configure HPL driver PGA settings.""" + if gain_db > 9: + return False + + value = (gain_db & 0x0F) << 3 + if unmute: value |= (1 << 2) + + self._write_register(REG_HPL_DRIVER, value) + return True + + def configure_hpr_pga(self, gain_db=0, unmute=True): + """Configure HPR driver PGA settings.""" + if gain_db > 9: + return False + + value = (gain_db & 0x0F) << 3 + if unmute: value |= (1 << 2) + + self._write_register(REG_HPR_DRIVER, value) + return True + + def configure_spk_pga(self, gain=SPK_GAIN_6DB, unmute=True): + """Configure Speaker driver settings.""" + value = (gain & 0x03) << 3 + if unmute: value |= (1 << 2) + + self._write_register(REG_SPK_DRIVER, value) + return True + + +class TLV320DAC3100: + """Driver for the TI TLV320DAC3100 Stereo DAC with Headphone Amplifier.""" + + def __init__(self, i2c, address=I2C_ADDR_DEFAULT): + """Initialize the TLV320DAC3100. + + :param i2c: The I2C bus the device is connected to + :param address: The I2C device address (default is 0x18) + """ + self._device = I2CDevice(i2c, address) + + # Initialize register page classes + self._page0 = Page0Registers(self._device) + self._page1 = Page1Registers(self._device) + + # Reset the device + if not self.reset(): + raise RuntimeError("Failed to reset TLV320DAC3100") + + # Basic properties and methods + + def reset(self): + """Reset the device.""" + return self._page0.reset() + + @property + def overtemperature(self): + """Check if the chip is overheating.""" + return self._page0.is_overtemperature() + + # PLL and Clock properties + + @property + def pll_clock_input(self): + """Get the PLL clock input source.""" + return self._page0.get_pll_clock_input() + + @pll_clock_input.setter + def pll_clock_input(self, clkin): + """Set the PLL clock input source.""" + self._page0.set_pll_clock_input(clkin) + + @property + def codec_clock_input(self): + """Get the CODEC clock input source.""" + return self._page0.get_codec_clock_input() + + @codec_clock_input.setter + def codec_clock_input(self, clkin): + """Set the CODEC clock input source.""" + self._page0.set_codec_clock_input(clkin) + + @property + def pll_power(self): + """Get the PLL power state.""" + return self._page0.get_pll_power() + + @pll_power.setter + def pll_power(self, on): + """Set the PLL power state.""" + self._page0.set_pll_power(on) + + # Speaker property + + @property + def speaker_enabled(self): + """Get the speaker amplifier state.""" + return self._page1.get_speaker_enabled() + + @speaker_enabled.setter + def speaker_enabled(self, enable): + """Enable or disable the speaker amplifier.""" + self._page1.set_speaker_enabled(enable) + + # Method-based API for complex operations + + def set_pll_values(self, p, r, j, d): + """Set the PLL P, R, J, and D values.""" + return self._page0.set_pll_values(p, r, j, d) + + def power_pll(self, on): + """Set the PLL power state.""" + return self._page0.set_pll_power(on) + + def set_ndac(self, enable, val): + """Set the NDAC value and enable/disable.""" + return self._page0.set_ndac(enable, val) + + def set_mdac(self, enable, val): + """Set the MDAC value and enable/disable.""" + return self._page0.set_mdac(enable, val) + + def set_codec_interface(self, format, data_len, bclk_out=False, wclk_out=False): + """Set the codec interface parameters.""" + return self._page0.set_codec_interface(format, data_len, bclk_out, wclk_out) + + def set_headset_detect(self, enable, detect_debounce=0, button_debounce=0): + """Configure headset detection settings.""" + return self._page0.set_headset_detect(enable, detect_debounce, button_debounce) + + def set_int1_source(self, headset_detect, button_press, dac_drc, + agc_noise, over_current, multiple_pulse): + """Configure the INT1 interrupt sources.""" + return self._page0.set_int1_source(headset_detect, button_press, dac_drc, + agc_noise, over_current, multiple_pulse) + + def set_gpio1_mode(self, mode): + """Set the GPIO1 pin mode.""" + return self._page0.set_gpio1_mode(mode) + + def set_dac_data_path(self, left_dac_on, right_dac_on, + left_path=DAC_PATH_NORMAL, + right_path=DAC_PATH_NORMAL, + volume_step=VOLUME_STEP_1SAMPLE): + """Configure the DAC data path settings.""" + return self._page0.set_dac_data_path(left_dac_on, right_dac_on, + left_path, right_path, volume_step) + + def configure_analog_inputs(self, left_dac=DAC_ROUTE_NONE, right_dac=DAC_ROUTE_NONE, + left_ain1=False, left_ain2=False, right_ain2=False, + hpl_routed_to_hpr=False): + """Configure DAC and analog input routing.""" + return self._page1.configure_analog_inputs(left_dac, right_dac, + left_ain1, left_ain2, + right_ain2, hpl_routed_to_hpr) + + def set_dac_volume_control(self, left_mute, right_mute, control=VOL_INDEPENDENT): + """Configure the DAC volume control settings.""" + return self._page0.set_dac_volume_control(left_mute, right_mute, control) + + def set_channel_volume(self, right_channel, db): + """Set DAC channel volume in dB.""" + return self._page0.set_channel_volume(right_channel, db) + + def configure_headphone_driver(self, left_powered, right_powered, + common=HP_COMMON_1_35V, power_down_on_scd=False): + """Configure headphone driver settings.""" + return self._page1.configure_headphone_driver(left_powered, right_powered, + common, power_down_on_scd) + + def set_hpl_volume(self, route_enabled, gain=0x7F): + """Set HPL analog volume control.""" + return self._page1.set_hpl_volume(route_enabled, gain) + + def configure_hpl_pga(self, gain_db=0, unmute=True): + """Configure HPL driver PGA settings.""" + return self._page1.configure_hpl_pga(gain_db, unmute) + + def set_hpr_volume(self, route_enabled, gain=0x7F): + """Set HPR analog volume control.""" + return self._page1.set_hpr_volume(route_enabled, gain) + + def configure_hpr_pga(self, gain_db=0, unmute=True): + """Configure HPR driver PGA settings.""" + return self._page1.configure_hpr_pga(gain_db, unmute) + + def enable_speaker(self, enable): + """Enable or disable the Class-D speaker amplifier.""" + return self._page1.set_speaker_enabled(enable) + + def configure_spk_pga(self, gain=SPK_GAIN_6DB, unmute=True): + """Configure Speaker driver settings.""" + return self._page1.configure_spk_pga(gain, unmute) + + def set_spk_volume(self, route_enabled, gain=0x7F): + """Set Speaker analog volume control.""" + return self._page1.set_spk_volume(route_enabled, gain) \ No newline at end of file diff --git a/examples/tlv320_sinetone.py b/examples/tlv320_sinetone.py new file mode 100644 index 0000000..3b95acc --- /dev/null +++ b/examples/tlv320_sinetone.py @@ -0,0 +1,107 @@ +# SPDX-FileCopyrightText: 2024 Liz Clark for Adafruit Industries +# SPDX-License-Identifier: MIT + +""" +CircuitPython TLV320DAC3100 I2S Tone playback example. +Configures the TLV320DAC3100 DAC via I2C, then plays a tone +for one second on, one second off, in a loop. +""" +import time +import array +import math +import audiocore +import board +import busio +import audiobusio +import adafruit_tlv320 + +# Initialize I2C for DAC configuration +i2c = busio.I2C(board.SCL, board.SDA) + +# Initialize and configure the TLV320DAC3100 +print("Initializing TLV320DAC3100...") +tlv = adafruit_tlv320.TLV320DAC3100(i2c) + +# Reset configuration +tlv.reset() + +# Configure the codec interface for I2S mode with 16-bit data +print("Configuring codec interface...") +tlv.set_codec_interface(adafruit_tlv320.FORMAT_I2S, adafruit_tlv320.DATA_LEN_16) + +# Configure PLL with BCLK as input +print("Configuring clocks...") +tlv.pll_clock_input = adafruit_tlv320.PLL_CLKIN_BCLK +tlv.codec_clock_input = adafruit_tlv320.CODEC_CLKIN_PLL + +# For a standard 44.1kHz sample rate with 16-bit stereo, BCLK is 1.4112MHz +# We need to configure PLL to generate the appropriate internal clocks +# These PLL settings are very dependent on your exact I2S configuration +# You may need to experiment with these values +tlv.set_pll_values(1, 1, 7, 6144) # Different PLL values to try +tlv.set_ndac(True, 4) +tlv.set_mdac(True, 1) + +# Power up the PLL +print("Powering up PLL...") +tlv.pll_power = True +time.sleep(0.1) # Give PLL time to stabilize + +# Set up DAC data path - explicitly enable both channels +print("Setting up DAC data path...") +tlv.set_dac_data_path(True, True, + adafruit_tlv320.DAC_PATH_NORMAL, + adafruit_tlv320.DAC_PATH_NORMAL) + +# Configure volume - ensure we're unmuted and at a reasonable level +print("Setting DAC volume...") +tlv.set_dac_volume_control(False, False) # Make sure both channels are unmuted +tlv.set_channel_volume(False, 0) # Left channel at 0dB (max) +tlv.set_channel_volume(True, 0) # Right channel at 0dB (max) + +# Set up analog routing - route DAC to headphone +print("Configuring analog inputs...") +tlv.configure_analog_inputs( + adafruit_tlv320.DAC_ROUTE_HP, # Route left DAC directly to headphone + adafruit_tlv320.DAC_ROUTE_HP # Route right DAC directly to headphone +) + +# Configure headphone driver - ensure it's powered up and unmuted +print("Configuring headphone drivers...") +tlv.configure_headphone_driver(True, True) # Power up both left and right drivers + +# Explicitly set headphone volume to a high level +tlv.set_hpl_volume(True, 0) # Enable route with gain of 20 +tlv.set_hpr_volume(True, 0) # Enable route with gain of 20 + +# Ensure the headphone drivers are unmuted with gain +tlv.configure_hpl_pga(-1, True) # Max gain (9dB), unmuted +tlv.configure_hpr_pga(-1, True) # Max gain (9dB), unmuted + +print("DAC configuration complete!") + +# Initialize I2S for audio playback +# Depending on your board, these pins may be different +# BCLK, WCLK/LRCLK, DATA +audio = audiobusio.I2SOut(board.I2S_BCLK, board.I2S_WS, board.I2S_DIN) + +# Generate a sine wave at a higher volume +tone_volume = 0.1 # Increased volume +frequency = 440 # 440 Hz tone (A4) +sample_rate = 8000 # Sample rate in Hz +length = sample_rate // frequency +sine_wave = array.array("h", [0] * length) +for i in range(length): + sine_wave[i] = int((math.sin(math.pi * 2 * i / length)) * tone_volume * (2 ** 15 - 1)) + +sine_wave_sample = audiocore.RawSample(sine_wave, sample_rate=sample_rate) + +print("Starting audio playback...") + +# Play the tone without stopping +audio.play(sine_wave_sample, loop=True) + +# Keep the program running +while True: + time.sleep(1) + print("Tone is playing... Ctrl+C to stop") \ No newline at end of file From 98eb909cfc93bdce106edcd0f15c5db6a0ef61ba Mon Sep 17 00:00:00 2001 From: Liz Date: Fri, 14 Mar 2025 13:46:45 -0400 Subject: [PATCH 02/11] full port, full test needs cleaning up, but driver is working --- adafruit_tlv320.py | 740 +++++++++++++++++++++++++++++++++++- examples/tlv320_fulltest.py | 217 +++++++++++ 2 files changed, 955 insertions(+), 2 deletions(-) create mode 100644 examples/tlv320_fulltest.py diff --git a/adafruit_tlv320.py b/adafruit_tlv320.py index 75053c2..7c086f5 100644 --- a/adafruit_tlv320.py +++ b/adafruit_tlv320.py @@ -63,6 +63,8 @@ REG_DAC_VOL_CTRL = const(0x40) REG_DAC_LVOL = const(0x41) REG_DAC_RVOL = const(0x42) REG_HEADSET_DETECT = const(0x43) +REG_VOL_ADC_CTRL = const(0x74) # VOL/MICDET-Pin SAR ADC Control Register +REG_VOL_ADC_READ = const(0x75) # VOL/MICDET-Pin Gain Register # Page 1 registers REG_HP_SPK_ERR_CTL = const(0x1E) @@ -78,6 +80,11 @@ REG_HPL_DRIVER = const(0x28) REG_HPR_DRIVER = const(0x29) REG_SPK_DRIVER = const(0x2A) REG_HP_DRIVER_CTRL = const(0x2C) +REG_MICBIAS = const(0x2E) # MICBIAS Configuration Register +REG_INPUT_CM = const(0x32) # Input Common Mode Settings Register + +# Page 3 registers +REG_TIMER_MCLK_DIV = const(0x10) # Timer Clock MCLK Divider Register # Default I2C address I2C_ADDR_DEFAULT = const(0x18) @@ -164,6 +171,14 @@ BTN_DEBOUNCE_8MS = const(0b01) # 8ms debounce (1ms clock) BTN_DEBOUNCE_16MS = const(0b10) # 16ms debounce (2ms clock) BTN_DEBOUNCE_32MS = const(0b11) # 32ms debounce (4ms clock) +# Clock divider input source options +CDIV_CLKIN_MCLK = const(0b000) # MCLK (device pin) +CDIV_CLKIN_BCLK = const(0b001) # BCLK (device pin) +CDIV_CLKIN_DIN = const(0b010) # DIN (for systems where DAC is not required) +CDIV_CLKIN_PLL = const(0b011) # PLL_CLK (generated on-chip) +CDIV_CLKIN_DAC = const(0b100) # DAC_CLK (DAC DSP clock - generated on-chip) +CDIV_CLKIN_DAC_MOD = const(0b101) # DAC_MOD_CLK (generated on-chip) + class PagedRegisterBase: """Base class for paged register access.""" @@ -276,7 +291,6 @@ class Page0Registers(PagedRegisterBase): return not ((self._read_register(REG_OT_FLAG) >> 1) & 0x01) # Methods with property potential - def get_pll_clock_input(self): """Get the PLL clock input source.""" return self._get_bits(REG_CLOCK_MUX1, 0x03, 2) @@ -441,6 +455,336 @@ class Page0Registers(PagedRegisterBase): self._write_register(REG_DAC_LVOL, reg_val & 0xFF) return True + + def get_clock_divider_input(self): + """Get the current clock divider input source. + + :return: The current clock divider input source + """ + return self._get_bits(REG_CLKOUT_MUX, 0x07, 3) + + def set_clock_divider_input(self, clkin): + """Set the clock divider input source. + + :param clkin: The clock input source to use + :return: True if successful + """ + return self._set_bits(REG_CLKOUT_MUX, 0x07, 3, clkin) + + def get_clkout_m(self, enabled=None, val=None): + """Get the CLKOUT M divider value and enabled state. + + :param enabled: Optional pointer to store enabled state + :param val: Optional pointer to store M value (1-128) + :return: Tuple of (enabled, val) if both parameters are None, otherwise True + """ + reg_value = self._read_register(REG_CLKOUT_M) + is_enabled = bool(reg_value & 0x80) + m_value = reg_value & 0x7F + if m_value == 0: + m_value = 128 # 0 represents 128 + + if enabled is not None: + enabled = is_enabled + if val is not None: + val = m_value + + if enabled is None and val is None: + return (is_enabled, m_value) + return True + + def get_bclk_offset(self): + """Get the BCLK data slot offset. + + :return: Current offset value (0-255) + """ + return self._read_register(REG_DATA_SLOT_OFFSET) + + def get_bclk_n(self, enabled=None, val=None): + """Get the BCLK N divider value and enabled state. + + :param enabled: Optional pointer to store enabled state + :param val: Optional pointer to store N value (1-128) + :return: Tuple of (enabled, val) if both parameters are None, otherwise True + """ + reg_value = self._read_register(REG_BCLK_N) + is_enabled = bool(reg_value & 0x80) + n_value = reg_value & 0x7F + if n_value == 0: + n_value = 128 # 0 represents 128 + + if enabled is not None: + enabled = is_enabled + if val is not None: + val = n_value + + if enabled is None and val is None: + return (is_enabled, n_value) + return True + + def get_pll_values(self): + """Get the current PLL P, R, J, and D values. + + :return: Tuple of (P, R, J, D) values + """ + # P & R register + pr_reg = self._read_register(REG_PLL_PROG_PR) + p_val = (pr_reg >> 4) & 0x07 # bits 6:4 + r_val = pr_reg & 0x0F # bits 3:0 + + # Convert 0 values to their max representations + p_val = 8 if p_val == 0 else p_val + r_val = 16 if r_val == 0 else r_val + + # J register + j_val = self._read_register(REG_PLL_PROG_J) & 0x3F # bits 5:0 + + # D MSB & LSB registers (14 bits total) + d_msb = self._read_register(REG_PLL_PROG_D_MSB) + d_lsb = self._read_register(REG_PLL_PROG_D_LSB) + d_val = (d_msb << 8) | d_lsb + + return (p_val, r_val, j_val, d_val) + + def get_ndac(self): + """Get the NDAC value and enabled state. + + :return: Tuple of (enabled, value) + """ + reg_value = self._read_register(REG_NDAC) + is_enabled = bool(reg_value & 0x80) + n_value = reg_value & 0x7F + if n_value == 0: + n_value = 128 # 0 represents 128 + + return (is_enabled, n_value) + + def get_mdac(self): + """Get the MDAC value and enabled state. + + :return: Tuple of (enabled, value) + """ + reg_value = self._read_register(REG_MDAC) + is_enabled = bool(reg_value & 0x80) + m_value = reg_value & 0x7F + if m_value == 0: + m_value = 128 # 0 represents 128 + + return (is_enabled, m_value) + + def get_dosr(self): + """Get the DOSR divider value. + + :return: Current DOSR value + """ + msb = self._read_register(REG_DOSR_MSB) + lsb = self._read_register(REG_DOSR_LSB) + value = (msb << 8) | lsb + + # 0 represents 1024 + return 1024 if value == 0 else value + + def set_dosr(self, val): + """Set the DOSR divider value. + + :param val: DOSR divider value (2-1024, except 1023) + :return: True if successful, False if failure + """ + # Validate input range + if val < 2 or val > 1024 or val == 1023: + return False + + # 0 represents 1024 + dosr_val = 0 if val == 1024 else val + + self._write_register(REG_DOSR_MSB, (dosr_val >> 8) & 0xFF) + self._write_register(REG_DOSR_LSB, dosr_val & 0xFF) + + return True + + def get_dac_flags(self): + """Get the DAC and output driver status flags. + + :return: Dictionary with status flags for various components + """ + # Read first flag register + flag_reg = self._read_register(REG_DAC_FLAG) + left_dac_powered = bool(flag_reg & (1 << 7)) # bit 7 + hpl_powered = bool(flag_reg & (1 << 5)) # bit 5 + left_classd_powered = bool(flag_reg & (1 << 4)) # bit 4 + right_dac_powered = bool(flag_reg & (1 << 3)) # bit 3 + hpr_powered = bool(flag_reg & (1 << 1)) # bit 1 + right_classd_powered = bool(flag_reg & (1 << 0)) # bit 0 + + # Read second flag register + flag2_reg = self._read_register(REG_DAC_FLAG2) + left_pga_gain_ok = bool(flag2_reg & (1 << 4)) # bit 4 + right_pga_gain_ok = bool(flag2_reg & (1 << 0)) # bit 0 + + return { + "left_dac_powered": left_dac_powered, + "hpl_powered": hpl_powered, + "left_classd_powered": left_classd_powered, + "right_dac_powered": right_dac_powered, + "hpr_powered": hpr_powered, + "right_classd_powered": right_classd_powered, + "left_pga_gain_ok": left_pga_gain_ok, + "right_pga_gain_ok": right_pga_gain_ok + } + + def get_gpio1_input(self): + """Get the current GPIO1 input value. + + :return: Current GPIO1 input state (True/False) + """ + return bool(self._get_bits(REG_GPIO1_CTRL, 0x01, 1)) + + def get_din_input(self): + """Get the current DIN input value. + + :return: Current DIN input state (True/False) + """ + return bool(self._get_bits(REG_DIN_CTRL, 0x01, 0)) + + def get_codec_interface(self): + """Get the current codec interface settings. + + :return: Dictionary with format, data_len, bclk_out, and wclk_out values + """ + reg_value = self._read_register(REG_CODEC_IF_CTRL1) + format_val = (reg_value >> 6) & 0x03 # bits 7:6 + data_len = (reg_value >> 4) & 0x03 # bits 5:4 + bclk_out = bool(reg_value & (1 << 3)) # bit 3 + wclk_out = bool(reg_value & (1 << 2)) # bit 2 + + return { + "format": format_val, + "data_len": data_len, + "bclk_out": bclk_out, + "wclk_out": wclk_out + } + + def get_dac_data_path(self): + """Get the current DAC data path configuration. + + :return: Dictionary with DAC data path settings + """ + reg_value = self._read_register(REG_DAC_DATAPATH) + left_dac_on = bool(reg_value & (1 << 7)) # bit 7 + right_dac_on = bool(reg_value & (1 << 6)) # bit 6 + left_path = (reg_value >> 4) & 0x03 # bits 5:4 + right_path = (reg_value >> 2) & 0x03 # bits 3:2 + volume_step = reg_value & 0x03 # bits 1:0 + + return { + "left_dac_on": left_dac_on, + "right_dac_on": right_dac_on, + "left_path": left_path, + "right_path": right_path, + "volume_step": volume_step + } + + def get_dac_volume_control(self): + """Get the current DAC volume control configuration. + + :return: Dictionary with volume control settings + """ + reg_value = self._read_register(REG_DAC_VOL_CTRL) + left_mute = bool(reg_value & (1 << 3)) # bit 3 + right_mute = bool(reg_value & (1 << 2)) # bit 2 + control = reg_value & 0x03 # bits 1:0 + + return { + "left_mute": left_mute, + "right_mute": right_mute, + "control": control + } + + def get_channel_volume(self, right_channel): + """Get DAC channel volume in dB. + + :param right_channel: True for right channel, False for left channel + :return: Current volume in dB + """ + reg = REG_DAC_RVOL if right_channel else REG_DAC_LVOL + reg_val = self._read_register(reg) + + # Convert to signed value if needed + if reg_val & 0x80: + steps = reg_val - 256 # Two's complement conversion + else: + steps = reg_val + + # Convert to dB (each step is 0.5 dB) + return steps * 0.5 + + def get_headset_status(self): + """Get current headset detection status. + + :return: Integer value representing headset status (0=none, 1=without mic, 3=with mic) + """ + status_bits = self._get_bits(REG_HEADSET_DETECT, 0x03, 5) + return status_bits + + def config_vol_adc(self, pin_control=False, use_mclk=False, hysteresis=0, rate=0): + """Configure the Volume/MicDet pin ADC. + + :param pin_control: Enable pin control of DAC volume + :param use_mclk: Use MCLK instead of internal RC oscillator + :param hysteresis: ADC hysteresis setting (0-2) + :param rate: ADC sampling rate (0-7) + :return: True if successful + """ + value = (1 if pin_control else 0) << 7 + value |= (1 if use_mclk else 0) << 6 + value |= (hysteresis & 0x03) << 4 + value |= (rate & 0x07) + + self._write_register(REG_VOL_ADC_CTRL, value) + return True + + def read_vol_adc_db(self): + """Read the current volume from the Volume ADC in dB. + + :return: Current volume in dB (+18 to -63 dB) + """ + raw_val = self._read_register(REG_VOL_ADC_READ) & 0x7F + + # Check for reserved value + if raw_val == 0x7F: + return 0.0 + + # Convert register value to dB + # 0x00 = +18dB, 0x24 = 0dB, 0x7E = -63dB + if raw_val <= 0x24: + # Positive or zero dB range + return 18.0 - (raw_val * 0.5) + else: + # Negative dB range + return -((raw_val - 0x24) * 0.5) + + def set_int2_source(self, headset_detect=False, button_press=False, dac_drc=False, + agc_noise=False, over_current=False, multiple_pulse=False): + """Configure the INT2 interrupt sources. + + :param headset_detect: Enable headset detection interrupt + :param button_press: Enable button press detection interrupt + :param dac_drc: Enable DAC DRC signal power interrupt + :param agc_noise: Enable DAC data overflow interrupt + :param over_current: Enable short circuit interrupt + :param multiple_pulse: If true, INT2 generates multiple pulses until flag read + :return: True if successful + """ + value = 0 + if headset_detect: value |= (1 << 7) + if button_press: value |= (1 << 6) + if dac_drc: value |= (1 << 5) + if over_current: value |= (1 << 3) + if agc_noise: value |= (1 << 2) + if multiple_pulse: value |= (1 << 0) + + self._write_register(REG_INT2_CTRL, value) + return True class Page1Registers(PagedRegisterBase): @@ -544,6 +888,134 @@ class Page1Registers(PagedRegisterBase): self._write_register(REG_SPK_DRIVER, value) return True + + def is_headphone_shorted(self): + """Check if headphone short circuit is detected. + + :return: True if short circuit detected, False if not + """ + # TODO: Need to confirm the exact register and bit for headphone short detection + # This is a placeholder based on available information + return False + + def is_speaker_shorted(self): + """Check if speaker short circuit is detected. + + :return: True if short circuit detected, False if not + """ + return bool(self._get_bits(REG_SPK_AMP, 0x01, 0)) + + def is_hpl_gain_applied(self): + """Check if all programmed gains have been applied to HPL. + + :return: True if gains applied, False if still ramping + """ + return bool(self._get_bits(REG_HPL_DRIVER, 0x01, 0)) + + def is_hpr_gain_applied(self): + """Check if all programmed gains have been applied to HPR. + + :return: True if gains applied, False if still ramping + """ + return bool(self._get_bits(REG_HPR_DRIVER, 0x01, 0)) + + def is_spk_gain_applied(self): + """Check if all programmed gains have been applied to Speaker. + + :return: True if gains applied, False if still ramping + """ + return bool(self._get_bits(REG_SPK_DRIVER, 0x01, 0)) + + def reset_speaker_on_scd(self, reset): + """Configure speaker reset behavior on short circuit detection. + + :param reset: True to reset speaker on short circuit, False to remain unchanged + :return: True if successful + """ + # Register is inverse of parameter (0 = reset, 1 = no reset) + return self._set_bits(REG_HP_SPK_ERR_CTL, 0x01, 1, 0 if reset else 1) + + def reset_headphone_on_scd(self, reset): + """Configure headphone reset behavior on short circuit detection. + + :param reset: True to reset headphone on short circuit, False to remain unchanged + :return: True if successful + """ + # Register is inverse of parameter (0 = reset, 1 = no reset) + return self._set_bits(REG_HP_SPK_ERR_CTL, 0x01, 0, 0 if reset else 1) + + def configure_headphone_pop(self, wait_for_powerdown=True, powerup_time=0x07, ramp_time=0x03): + """Configure headphone pop removal settings. + + :param wait_for_powerdown: Wait for amp powerdown before DAC powerdown + :param powerup_time: Driver power-on time (0-11) + :param ramp_time: Driver ramp-up step time (0-3) + :return: True if successful + """ + value = (1 if wait_for_powerdown else 0) << 7 + value |= (powerup_time & 0x0F) << 3 + value |= (ramp_time & 0x03) << 1 + + self._write_register(REG_HP_POP, value) + return True + + def set_speaker_wait_time(self, wait_time=0): + """Set speaker power-up wait time. + + :param wait_time: Speaker power-up wait duration (0-7) + :return: True if successful + """ + return self._set_bits(REG_PGA_RAMP, 0x07, 4, wait_time) + + def headphone_lineout(self, left, right): + """Configure headphone outputs as line-out. + + :param left: Configure left channel as line-out + :param right: Configure right channel as line-out + :return: True if successful + """ + value = 0 + if left: value |= (1 << 2) + if right: value |= (1 << 1) + + self._write_register(REG_HP_DRIVER_CTRL, value) + return True + + def config_mic_bias(self, power_down=False, always_on=False, voltage=0): + """Configure MICBIAS settings.""" + value = (1 if power_down else 0) << 7 + value |= (1 if always_on else 0) << 3 + value |= (voltage & 0x03) + + self._write_register(REG_MICBIAS, value) # Using constant instead of 0x2E + return True + + def set_input_common_mode(self, ain1_cm, ain2_cm): + """Set analog input common mode connections.""" + value = 0 + if ain1_cm: value |= (1 << 7) + if ain2_cm: value |= (1 << 6) + + self._write_register(REG_INPUT_CM, value) # Using constant instead of 0x32 + return True + +class Page3Registers(PagedRegisterBase): + """Page 3 registers containing timer settings.""" + + def __init__(self, i2c_device): + """Initialize Page 3 registers. + + :param i2c_device: The I2C device + """ + super().__init__(i2c_device, 3) + + def config_delay_divider(self, use_mclk=True, divider=1): + """Configure programmable delay timer clock source and divider.""" + value = (1 if use_mclk else 0) << 7 + value |= (divider & 0x7F) + + self._write_register(REG_TIMER_MCLK_DIV, value) # Using constant instead of 0x10 + return True class TLV320DAC3100: @@ -560,6 +1032,7 @@ class TLV320DAC3100: # Initialize register page classes self._page0 = Page0Registers(self._device) self._page1 = Page1Registers(self._device) + self._page3 = Page3Registers(self._device) # Reset the device if not self.reset(): @@ -712,4 +1185,267 @@ class TLV320DAC3100: def set_spk_volume(self, route_enabled, gain=0x7F): """Set Speaker analog volume control.""" - return self._page1.set_spk_volume(route_enabled, gain) \ No newline at end of file + return self._page1.set_spk_volume(route_enabled, gain) + + @property + def clock_divider_input(self): + """Get the current clock divider input source.""" + return self._page0.get_clock_divider_input() + + @clock_divider_input.setter + def clock_divider_input(self, clkin): + """Set the clock divider input source.""" + self._page0.set_clock_divider_input(clkin) + + def get_clkout_m(self): + """Get the CLKOUT M divider value and enabled state. + + :return: Tuple of (enabled, value) + """ + return self._page0.get_clkout_m() + + def get_bclk_offset(self): + """Get the BCLK data slot offset. + + :return: Current offset value (0-255) + """ + return self._page0.get_bclk_offset() + + def get_bclk_n(self): + """Get the BCLK N divider value and enabled state. + + :return: Tuple of (enabled, value) + """ + return self._page0.get_bclk_n() + + def get_pll_values(self): + """Get the current PLL P, R, J, and D values. + + :return: Tuple of (P, R, J, D) values + """ + return self._page0.get_pll_values() + + def get_ndac(self): + """Get the NDAC value and enabled state. + + :return: Tuple of (enabled, value) + """ + return self._page0.get_ndac() + + def get_mdac(self): + """Get the MDAC value and enabled state. + + :return: Tuple of (enabled, value) + """ + return self._page0.get_mdac() + + def get_dosr(self): + """Get the DOSR divider value. + + :return: Current DOSR value + """ + return self._page0.get_dosr() + + def set_dosr(self, val): + """Set the DOSR divider value. + + :param val: DOSR divider value (2-1024, except 1023) + :return: True if successful, False if failure + """ + return self._page0.set_dosr(val) + + def get_dac_flags(self): + """Get the DAC and output driver status flags. + + :return: Dictionary with status flags for various components + """ + return self._page0.get_dac_flags() + + def get_gpio1_input(self): + """Get the current GPIO1 input value. + + :return: Current GPIO1 input state (True/False) + """ + return self._page0.get_gpio1_input() + + def get_din_input(self): + """Get the current DIN input value. + + :return: Current DIN input state (True/False) + """ + return self._page0.get_din_input() + + def get_codec_interface(self): + """Get the current codec interface settings. + + :return: Dictionary with format, data_len, bclk_out, and wclk_out values + """ + return self._page0.get_codec_interface() + + def get_dac_data_path(self): + """Get the current DAC data path configuration. + + :return: Dictionary with DAC data path settings + """ + return self._page0.get_dac_data_path() + + def get_dac_volume_control(self): + """Get the current DAC volume control configuration. + + :return: Dictionary with volume control settings + """ + return self._page0.get_dac_volume_control() + + def get_channel_volume(self, right_channel): + """Get DAC channel volume in dB. + + :param right_channel: True for right channel, False for left channel + :return: Current volume in dB + """ + return self._page0.get_channel_volume(right_channel) + + def is_headphone_shorted(self): + """Check if headphone short circuit is detected. + + :return: True if short circuit detected, False if not + """ + return self._page1.is_headphone_shorted() + + def is_speaker_shorted(self): + """Check if speaker short circuit is detected. + + :return: True if short circuit detected, False if not + """ + return self._page1.is_speaker_shorted() + + def is_hpl_gain_applied(self): + """Check if all programmed gains have been applied to HPL. + + :return: True if gains applied, False if still ramping + """ + return self._page1.is_hpl_gain_applied() + + def is_hpr_gain_applied(self): + """Check if all programmed gains have been applied to HPR. + + :return: True if gains applied, False if still ramping + """ + return self._page1.is_hpr_gain_applied() + + def is_spk_gain_applied(self): + """Check if all programmed gains have been applied to Speaker. + + :return: True if gains applied, False if still ramping + """ + return self._page1.is_spk_gain_applied() + + def get_headset_status(self): + """Get current headset detection status. + + :return: Integer value representing headset status (0=none, 1=without mic, 3=with mic) + """ + return self._page0.get_headset_status() + + def reset_speaker_on_scd(self, reset): + """Configure speaker reset behavior on short circuit detection. + + :param reset: True to reset speaker on short circuit, False to remain unchanged + :return: True if successful + """ + return self._page1.reset_speaker_on_scd(reset) + + def reset_headphone_on_scd(self, reset): + """Configure headphone reset behavior on short circuit detection. + + :param reset: True to reset headphone on short circuit, False to remain unchanged + :return: True if successful + """ + return self._page1.reset_headphone_on_scd(reset) + + def configure_headphone_pop(self, wait_for_powerdown=True, powerup_time=0x07, ramp_time=0x03): + """Configure headphone pop removal settings. + + :param wait_for_powerdown: Wait for amp powerdown before DAC powerdown + :param powerup_time: Driver power-on time (0-11) + :param ramp_time: Driver ramp-up step time (0-3) + :return: True if successful + """ + return self._page1.configure_headphone_pop(wait_for_powerdown, powerup_time, ramp_time) + + def set_speaker_wait_time(self, wait_time=0): + """Set speaker power-up wait time. + + :param wait_time: Speaker power-up wait duration (0-7) + :return: True if successful + """ + return self._page1.set_speaker_wait_time(wait_time) + + def headphone_lineout(self, left, right): + """Configure headphone outputs as line-out. + + :param left: Configure left channel as line-out + :param right: Configure right channel as line-out + :return: True if successful + """ + return self._page1.headphone_lineout(left, right) + + def config_mic_bias(self, power_down=False, always_on=False, voltage=0): + """Configure MICBIAS settings. + + :param power_down: Enable software power down + :param always_on: Keep MICBIAS on even without headset + :param voltage: MICBIAS voltage setting (0-3) + :return: True if successful + """ + return self._page1.config_mic_bias(power_down, always_on, voltage) + + def set_input_common_mode(self, ain1_cm, ain2_cm): + """Set analog input common mode connections. + + :param ain1_cm: Connect AIN1 to common mode when unused + :param ain2_cm: Connect AIN2 to common mode when unused + :return: True if successful + """ + return self._page1.set_input_common_mode(ain1_cm, ain2_cm) + + def config_delay_divider(self, use_mclk=True, divider=1): + """Configure programmable delay timer clock source and divider. + + :param use_mclk: True to use external MCLK, False for internal oscillator + :param divider: Clock divider (1-127, or 0 for 128) + :return: True if successful + """ + return self._page3.config_delay_divider(use_mclk, divider) + + def config_vol_adc(self, pin_control=False, use_mclk=False, hysteresis=0, rate=0): + """Configure the Volume/MicDet pin ADC. + + :param pin_control: Enable pin control of DAC volume + :param use_mclk: Use MCLK instead of internal RC oscillator + :param hysteresis: ADC hysteresis setting (0-2) + :param rate: ADC sampling rate (0-7) + :return: True if successful + """ + return self._page0.config_vol_adc(pin_control, use_mclk, hysteresis, rate) + + def read_vol_adc_db(self): + """Read the current volume from the Volume ADC in dB. + + :return: Current volume in dB (+18 to -63 dB) + """ + return self._page0.read_vol_adc_db() + + def set_int2_source(self, headset_detect=False, button_press=False, dac_drc=False, + agc_noise=False, over_current=False, multiple_pulse=False): + """Configure the INT2 interrupt sources. + + :param headset_detect: Enable headset detection interrupt + :param button_press: Enable button press detection interrupt + :param dac_drc: Enable DAC DRC signal power interrupt + :param agc_noise: Enable DAC data overflow interrupt + :param over_current: Enable short circuit interrupt + :param multiple_pulse: If true, INT2 generates multiple pulses until flag read + :return: True if successful + """ + return self._page0.set_int2_source(headset_detect, button_press, dac_drc, + agc_noise, over_current, multiple_pulse) \ No newline at end of file diff --git a/examples/tlv320_fulltest.py b/examples/tlv320_fulltest.py new file mode 100644 index 0000000..1c0eeb2 --- /dev/null +++ b/examples/tlv320_fulltest.py @@ -0,0 +1,217 @@ +# SPDX-FileCopyrightText: 2024 Your Name +# SPDX-License-Identifier: MIT + +""" +Comprehensive test for TLV320DAC3100 CircuitPython driver. +Tests all implemented functionality and plays a sine wave. +""" + +import time +import array +import math +import audiocore +import board +import busio +import audiobusio +import adafruit_tlv320 + +def print_test_header(name): + """Print a test section header.""" + print("\n" + "="*40) + print(f"Testing {name}") + print("="*40) + +def test_result(name, result): + """Print result of a test.""" + if result: + print(f"✓ {name}: Success") + else: + print(f"✗ {name}: Failure") + return result + +# Initialize I2C bus +print("Initializing I2C...") +i2c = busio.I2C(board.SCL, board.SDA) + +# Initialize the TLV320DAC3100 +print("Initializing TLV320DAC3100...") +try: + tlv = adafruit_tlv320.TLV320DAC3100(i2c) + print("DAC initialized successfully!") +except Exception as e: + print(f"Failed to initialize TLV320DAC3100: {e}") + raise + +# Reset the device +print_test_header("Reset") +test_result("Reset DAC", tlv.reset()) + +# Test basic health functions +print_test_header("Basic Health") +print(f"Overtemperature status: {'ALERT!' if tlv.overtemperature else 'OK'}") + +# Test Clock Configuration +print_test_header("Clock Configuration") + +# Set PLL input and codec input +tlv.pll_clock_input = adafruit_tlv320.PLL_CLKIN_BCLK +tlv.codec_clock_input = adafruit_tlv320.CODEC_CLKIN_PLL +print(f"PLL clock input set to: {tlv.pll_clock_input}") +print(f"CODEC clock input set to: {tlv.codec_clock_input}") + +# Configure clock divider input +tlv.clock_divider_input = adafruit_tlv320.CDIV_CLKIN_PLL +print(f"Clock divider input: {tlv.clock_divider_input}") + +# Set PLL values +test_result("Set PLL values", tlv.set_pll_values(1, 1, 7, 6144)) +pll_vals = tlv.get_pll_values() +print(f"PLL values: P={pll_vals[0]}, R={pll_vals[1]}, J={pll_vals[2]}, D={pll_vals[3]}") + +# Set DAC clock dividers +test_result("Set NDAC", tlv.set_ndac(True, 4)) +ndac_vals = tlv.get_ndac() +print(f"NDAC: enabled={ndac_vals[0]}, value={ndac_vals[1]}") + +test_result("Set MDAC", tlv.set_mdac(True, 1)) +mdac_vals = tlv.get_mdac() +print(f"MDAC: enabled={mdac_vals[0]}, value={mdac_vals[1]}") + +test_result("Set DOSR", tlv.set_dosr(256)) +dosr_val = tlv.get_dosr() +print(f"DOSR value: {dosr_val}") + +# Power up the PLL +tlv.pll_power = True +print(f"PLL power state: {'ON' if tlv.pll_power else 'OFF'}") + +# Test GPIO and interrupt configuration +print_test_header("GPIO and Interrupts") + +test_result("Set GPIO1 mode", tlv.set_gpio1_mode(adafruit_tlv320.GPIO1_INT1)) +test_result("Set INT1 sources", tlv.set_int1_source(True, False, False, False, False, False)) +test_result("Set INT2 sources", tlv.set_int2_source(True, False, False, False, False, False)) +print(f"GPIO1 input state: {tlv.get_gpio1_input()}") +print(f"DIN input state: {tlv.get_din_input()}") + +# Test codec interface configuration +print_test_header("Codec Interface") + +test_result("Set codec interface", tlv.set_codec_interface(adafruit_tlv320.FORMAT_I2S, adafruit_tlv320.DATA_LEN_16)) +codec_if = tlv.get_codec_interface() +print(f"Codec interface: {codec_if}") + +# Test DAC path configuration +print_test_header("DAC Configuration") + +test_result("Set DAC data path", tlv.set_dac_data_path(True, True)) +dac_path = tlv.get_dac_data_path() +print(f"DAC data path: {dac_path}") + +test_result("Set DAC volume control", tlv.set_dac_volume_control(False, False, adafruit_tlv320.VOL_INDEPENDENT)) +vol_ctrl = tlv.get_dac_volume_control() +print(f"DAC volume control: {vol_ctrl}") + +test_result("Set left channel volume", tlv.set_channel_volume(False, 0)) +left_vol = tlv.get_channel_volume(False) +print(f"Left DAC volume: {left_vol} dB") + +test_result("Set right channel volume", tlv.set_channel_volume(True, 0)) +right_vol = tlv.get_channel_volume(True) +print(f"Right DAC volume: {right_vol} dB") + +# Test headphone and speaker configuration +print_test_header("Headphone and Speaker") + +test_result("Configure headphone driver", tlv.configure_headphone_driver(True, True)) +test_result("Configure analog inputs", tlv.configure_analog_inputs(adafruit_tlv320.DAC_ROUTE_HP, adafruit_tlv320.DAC_ROUTE_HP)) + +test_result("Set HPL volume", tlv.set_hpl_volume(True, 20)) +test_result("Configure HPL PGA", tlv.configure_hpl_pga(9, True)) +print(f"HPL gain applied: {tlv.is_hpl_gain_applied()}") + +test_result("Set HPR volume", tlv.set_hpr_volume(True, 20)) +test_result("Configure HPR PGA", tlv.configure_hpr_pga(9, True)) +print(f"HPR gain applied: {tlv.is_hpr_gain_applied()}") + +tlv.speaker_enabled = True +print(f"Speaker enabled: {tlv.speaker_enabled}") +test_result("Configure SPK PGA", tlv.configure_spk_pga(adafruit_tlv320.SPK_GAIN_6DB, True)) +test_result("Set SPK volume", tlv.set_spk_volume(False, 20)) +print(f"SPK gain applied: {tlv.is_spk_gain_applied()}") +print(f"Speaker shorted: {tlv.is_speaker_shorted()}") + +# Test headset detection +print_test_header("Headset Detection") + +test_result("Set headset detect", tlv.set_headset_detect(True)) +headset_status = tlv.get_headset_status() +print(f"Headset status: {headset_status}") + +# Test hardware configuration +print_test_header("Hardware Configuration") + +test_result("Reset speaker on SCD", tlv.reset_speaker_on_scd(True)) +test_result("Reset headphone on SCD", tlv.reset_headphone_on_scd(True)) +test_result("Configure headphone pop", tlv.configure_headphone_pop(True, 0x07, 0x03)) +test_result("Set speaker wait time", tlv.set_speaker_wait_time(0x02)) +test_result("Configure headphone as lineout", tlv.headphone_lineout(False, False)) +test_result("Configure mic bias", tlv.config_mic_bias(False, False, 0x01)) +test_result("Set input common mode", tlv.set_input_common_mode(True, True)) +test_result("Configure delay divider", tlv.config_delay_divider(True, 1)) + +# Test Volume ADC +print_test_header("Volume ADC") + +test_result("Configure Volume ADC", tlv.config_vol_adc(False, True, 0, 0)) +vol_adc = tlv.read_vol_adc_db() +print(f"Volume ADC reading: {vol_adc} dB") + +# Get DAC flags +print_test_header("DAC Status Flags") + +dac_flags = tlv.get_dac_flags() +print("DAC Flags:") +for key, value in dac_flags.items(): + print(f" {key}: {value}") + +# Initialize I2S for audio playback +print_test_header("Audio Playback") +print("Initializing I2S...") + +try: + # BCLK, WCLK/LRCLK, DATA - adjust pins as needed for your board + audio = audiobusio.I2SOut(board.I2S_BCLK, board.I2S_WS, board.I2S_DIN) + + # Generate a sine wave + tone_volume = 0.5 # Higher volume + frequency = 440 # 440 Hz tone (A4) + sample_rate = 44100 # CD-quality sample rate + length = sample_rate // frequency + sine_wave = array.array("h", [0] * length) + for i in range(length): + sine_wave[i] = int((math.sin(math.pi * 2 * i / length)) * tone_volume * (2 ** 15 - 1)) + + sine_wave_sample = audiocore.RawSample(sine_wave, sample_rate=sample_rate) + + print("Starting audio playback...") + + # Continuous playback + audio.play(sine_wave_sample, loop=True) + + # Main loop + print("All tests completed! Playing tone continuously.") + print("Press Ctrl+C to stop.") + + while True: + # Check for overtemperature every 5 seconds + if tlv.overtemperature: + print("WARNING: DAC is overheating!") + time.sleep(5) + +except Exception as e: + print(f"Audio error: {e}") + + # If audio setup fails, just loop + while True: + time.sleep(1) From 5af8082cfacd787215ccbcb448480a2a2bfc0f87 Mon Sep 17 00:00:00 2001 From: Liz Date: Tue, 18 Mar 2025 11:32:51 -0400 Subject: [PATCH 03/11] propertized and simpletest --- adafruit_tlv320.py | 1444 ++++++++++++++++++++------------- examples/tlv320_simpletest.py | 46 +- 2 files changed, 905 insertions(+), 585 deletions(-) diff --git a/adafruit_tlv320.py b/adafruit_tlv320.py index 7c086f5..75692c7 100644 --- a/adafruit_tlv320.py +++ b/adafruit_tlv320.py @@ -179,6 +179,12 @@ CDIV_CLKIN_PLL = const(0b011) # PLL_CLK (generated on-chip) CDIV_CLKIN_DAC = const(0b100) # DAC_CLK (DAC DSP clock - generated on-chip) CDIV_CLKIN_DAC_MOD = const(0b101) # DAC_MOD_CLK (generated on-chip) +# Audio presets +# Audio presets +PRESET_MID_QUALITY = const(0) # 22.05kHz, 16-bit (good for CircuitPython audio) +PRESET_CD_QUALITY = const(1) # 44.1kHz, 16-bit +PRESET_DVD_QUALITY = const(2) # 48kHz, 24-bit +PRESET_HIRES_QUALITY = const(3) # 96kHz, 24-bit class PagedRegisterBase: """Base class for paged register access.""" @@ -268,8 +274,8 @@ class Page0Registers(PagedRegisterBase): """ super().__init__(i2c_device, 0) - def reset(self): - """Perform a software reset of the chip. + def _reset(self): + """Perform a software _reset of the chip. :return: True if successful, False otherwise """ @@ -282,7 +288,7 @@ class Page0Registers(PagedRegisterBase): # Reset bit should be 0 after reset completes return self._read_register(REG_RESET) == 0 - def is_overtemperature(self): + def _is_overtemperature(self): """Check if the chip is in an over-temperature condition. :return: True if overtemp condition exists, False if temperature is OK @@ -290,100 +296,7 @@ class Page0Registers(PagedRegisterBase): # Bit 1 of register 3 is the overtemp flag (0 = overtemp, 1 = normal) return not ((self._read_register(REG_OT_FLAG) >> 1) & 0x01) - # Methods with property potential - def get_pll_clock_input(self): - """Get the PLL clock input source.""" - return self._get_bits(REG_CLOCK_MUX1, 0x03, 2) - - def set_pll_clock_input(self, clkin): - """Set the PLL clock input source.""" - return self._set_bits(REG_CLOCK_MUX1, 0x03, 2, clkin) - - def get_codec_clock_input(self): - """Get the CODEC clock input source.""" - return self._get_bits(REG_CLOCK_MUX1, 0x03, 0) - - def set_codec_clock_input(self, clkin): - """Set the CODEC clock input source.""" - return self._set_bits(REG_CLOCK_MUX1, 0x03, 0, clkin) - - def get_pll_power(self): - """Get the PLL power state.""" - return bool(self._get_bits(REG_PLL_PROG_PR, 0x01, 7)) - - def set_pll_power(self, on): - """Set the PLL power state.""" - return self._set_bits(REG_PLL_PROG_PR, 0x01, 7, 1 if on else 0) - - # Other methods - - def set_pll_values(self, p, r, j, d): - """Set the PLL P, R, J, and D values. - - :param p: PLL P value (1-8) - :param r: PLL R value (1-16) - :param j: PLL J value (1-63) - :param d: PLL D value (0-9999) - :return: True if successful, False if failure - """ - # Validate all input ranges - if p < 1 or p > 8: - return False - if r < 1 or r > 16: - return False - if j < 1 or j > 63: - return False - if d > 9999: - return False - - # P & R register - p_val = p % 8 # P values wrap at 8 - r_val = r % 16 # R values wrap at 16 - - # Write P (bits 6:4) and R (bits 3:0) values - pr_value = (p_val << 4) | r_val - self._write_register(REG_PLL_PROG_PR, pr_value) - - # J register (bits 5:0) - self._write_register(REG_PLL_PROG_J, j & 0x3F) - - # D MSB & LSB registers (14 bits total) - self._write_register(REG_PLL_PROG_D_MSB, (d >> 8) & 0xFF) - self._write_register(REG_PLL_PROG_D_LSB, d & 0xFF) - - return True - - def set_ndac(self, enable, val): - """Set the NDAC value and enable/disable.""" - # Validate input range - if val < 1 or val > 128: - return False - - value = ((1 if enable else 0) << 7) | (val % 128) - self._write_register(REG_NDAC, value) - return True - - def set_mdac(self, enable, val): - """Set the MDAC value and enable/disable.""" - # Validate input range - if val < 1 or val > 128: - return False - - value = ((1 if enable else 0) << 7) | (val % 128) - self._write_register(REG_MDAC, value) - return True - - def set_codec_interface(self, format, data_len, bclk_out=False, wclk_out=False): - """Set the codec interface parameters.""" - value = (format & 0x03) << 6 - value |= (data_len & 0x03) << 4 - value |= (1 if bclk_out else 0) << 3 - value |= (1 if wclk_out else 0) << 2 - - self._write_register(REG_CODEC_IF_CTRL1, value) - return True - - def set_int1_source(self, headset_detect, button_press, dac_drc, + def _set_int1_source(self, headset_detect, button_press, dac_drc, agc_noise, over_current, multiple_pulse): """Configure the INT1 interrupt sources.""" value = 0 @@ -397,11 +310,11 @@ class Page0Registers(PagedRegisterBase): self._write_register(REG_INT1_CTRL, value) return True - def set_gpio1_mode(self, mode): + def _set_gpio1_mode(self, mode): """Set the GPIO1 pin mode.""" return self._set_bits(REG_GPIO1_CTRL, 0x0F, 2, mode) - def set_headset_detect(self, enable, detect_debounce=0, button_debounce=0): + def _set_headset_detect(self, enable, detect_debounce=0, button_debounce=0): """Configure headset detection settings.""" value = (1 if enable else 0) << 7 value |= (detect_debounce & 0x07) << 2 @@ -410,7 +323,7 @@ class Page0Registers(PagedRegisterBase): self._write_register(REG_HEADSET_DETECT, value) return True - def set_dac_data_path(self, left_dac_on, right_dac_on, + def _set_dac_data_path(self, left_dac_on, right_dac_on, left_path=DAC_PATH_NORMAL, right_path=DAC_PATH_NORMAL, volume_step=VOLUME_STEP_1SAMPLE): @@ -425,7 +338,7 @@ class Page0Registers(PagedRegisterBase): self._write_register(REG_DAC_DATAPATH, value) return True - def set_dac_volume_control(self, left_mute, right_mute, control=VOL_INDEPENDENT): + def _set_dac_volume_control(self, left_mute, right_mute, control=VOL_INDEPENDENT): """Configure the DAC volume control settings.""" value = 0 if left_mute: value |= (1 << 3) @@ -435,7 +348,7 @@ class Page0Registers(PagedRegisterBase): self._write_register(REG_DAC_VOL_CTRL, value) return True - def set_channel_volume(self, right_channel, db): + def _set_channel_volume(self, right_channel, db): """Set DAC channel volume in dB.""" # Constrain input to valid range if db > 24.0: @@ -456,153 +369,7 @@ class Page0Registers(PagedRegisterBase): return True - def get_clock_divider_input(self): - """Get the current clock divider input source. - - :return: The current clock divider input source - """ - return self._get_bits(REG_CLKOUT_MUX, 0x07, 3) - - def set_clock_divider_input(self, clkin): - """Set the clock divider input source. - - :param clkin: The clock input source to use - :return: True if successful - """ - return self._set_bits(REG_CLKOUT_MUX, 0x07, 3, clkin) - - def get_clkout_m(self, enabled=None, val=None): - """Get the CLKOUT M divider value and enabled state. - - :param enabled: Optional pointer to store enabled state - :param val: Optional pointer to store M value (1-128) - :return: Tuple of (enabled, val) if both parameters are None, otherwise True - """ - reg_value = self._read_register(REG_CLKOUT_M) - is_enabled = bool(reg_value & 0x80) - m_value = reg_value & 0x7F - if m_value == 0: - m_value = 128 # 0 represents 128 - - if enabled is not None: - enabled = is_enabled - if val is not None: - val = m_value - - if enabled is None and val is None: - return (is_enabled, m_value) - return True - - def get_bclk_offset(self): - """Get the BCLK data slot offset. - - :return: Current offset value (0-255) - """ - return self._read_register(REG_DATA_SLOT_OFFSET) - - def get_bclk_n(self, enabled=None, val=None): - """Get the BCLK N divider value and enabled state. - - :param enabled: Optional pointer to store enabled state - :param val: Optional pointer to store N value (1-128) - :return: Tuple of (enabled, val) if both parameters are None, otherwise True - """ - reg_value = self._read_register(REG_BCLK_N) - is_enabled = bool(reg_value & 0x80) - n_value = reg_value & 0x7F - if n_value == 0: - n_value = 128 # 0 represents 128 - - if enabled is not None: - enabled = is_enabled - if val is not None: - val = n_value - - if enabled is None and val is None: - return (is_enabled, n_value) - return True - - def get_pll_values(self): - """Get the current PLL P, R, J, and D values. - - :return: Tuple of (P, R, J, D) values - """ - # P & R register - pr_reg = self._read_register(REG_PLL_PROG_PR) - p_val = (pr_reg >> 4) & 0x07 # bits 6:4 - r_val = pr_reg & 0x0F # bits 3:0 - - # Convert 0 values to their max representations - p_val = 8 if p_val == 0 else p_val - r_val = 16 if r_val == 0 else r_val - - # J register - j_val = self._read_register(REG_PLL_PROG_J) & 0x3F # bits 5:0 - - # D MSB & LSB registers (14 bits total) - d_msb = self._read_register(REG_PLL_PROG_D_MSB) - d_lsb = self._read_register(REG_PLL_PROG_D_LSB) - d_val = (d_msb << 8) | d_lsb - - return (p_val, r_val, j_val, d_val) - - def get_ndac(self): - """Get the NDAC value and enabled state. - - :return: Tuple of (enabled, value) - """ - reg_value = self._read_register(REG_NDAC) - is_enabled = bool(reg_value & 0x80) - n_value = reg_value & 0x7F - if n_value == 0: - n_value = 128 # 0 represents 128 - - return (is_enabled, n_value) - - def get_mdac(self): - """Get the MDAC value and enabled state. - - :return: Tuple of (enabled, value) - """ - reg_value = self._read_register(REG_MDAC) - is_enabled = bool(reg_value & 0x80) - m_value = reg_value & 0x7F - if m_value == 0: - m_value = 128 # 0 represents 128 - - return (is_enabled, m_value) - - def get_dosr(self): - """Get the DOSR divider value. - - :return: Current DOSR value - """ - msb = self._read_register(REG_DOSR_MSB) - lsb = self._read_register(REG_DOSR_LSB) - value = (msb << 8) | lsb - - # 0 represents 1024 - return 1024 if value == 0 else value - - def set_dosr(self, val): - """Set the DOSR divider value. - - :param val: DOSR divider value (2-1024, except 1023) - :return: True if successful, False if failure - """ - # Validate input range - if val < 2 or val > 1024 or val == 1023: - return False - - # 0 represents 1024 - dosr_val = 0 if val == 1024 else val - - self._write_register(REG_DOSR_MSB, (dosr_val >> 8) & 0xFF) - self._write_register(REG_DOSR_LSB, dosr_val & 0xFF) - - return True - - def get_dac_flags(self): + def _get_dac_flags(self): """Get the DAC and output driver status flags. :return: Dictionary with status flags for various components @@ -639,14 +406,14 @@ class Page0Registers(PagedRegisterBase): """ return bool(self._get_bits(REG_GPIO1_CTRL, 0x01, 1)) - def get_din_input(self): + def _get_din_input(self): """Get the current DIN input value. :return: Current DIN input state (True/False) """ return bool(self._get_bits(REG_DIN_CTRL, 0x01, 0)) - def get_codec_interface(self): + def _get_codec_interface(self): """Get the current codec interface settings. :return: Dictionary with format, data_len, bclk_out, and wclk_out values @@ -664,7 +431,7 @@ class Page0Registers(PagedRegisterBase): "wclk_out": wclk_out } - def get_dac_data_path(self): + def _get_dac_data_path(self): """Get the current DAC data path configuration. :return: Dictionary with DAC data path settings @@ -684,7 +451,7 @@ class Page0Registers(PagedRegisterBase): "volume_step": volume_step } - def get_dac_volume_control(self): + def _get_dac_volume_control(self): """Get the current DAC volume control configuration. :return: Dictionary with volume control settings @@ -700,7 +467,7 @@ class Page0Registers(PagedRegisterBase): "control": control } - def get_channel_volume(self, right_channel): + def _get_channel_volume(self, right_channel): """Get DAC channel volume in dB. :param right_channel: True for right channel, False for left channel @@ -718,7 +485,7 @@ class Page0Registers(PagedRegisterBase): # Convert to dB (each step is 0.5 dB) return steps * 0.5 - def get_headset_status(self): + def _get_headset_status(self): """Get current headset detection status. :return: Integer value representing headset status (0=none, 1=without mic, 3=with mic) @@ -726,7 +493,7 @@ class Page0Registers(PagedRegisterBase): status_bits = self._get_bits(REG_HEADSET_DETECT, 0x03, 5) return status_bits - def config_vol_adc(self, pin_control=False, use_mclk=False, hysteresis=0, rate=0): + def _config_vol_adc(self, pin_control=False, use_mclk=False, hysteresis=0, rate=0): """Configure the Volume/MicDet pin ADC. :param pin_control: Enable pin control of DAC volume @@ -743,7 +510,7 @@ class Page0Registers(PagedRegisterBase): self._write_register(REG_VOL_ADC_CTRL, value) return True - def read_vol_adc_db(self): + def _read_vol_adc_db(self): """Read the current volume from the Volume ADC in dB. :return: Current volume in dB (+18 to -63 dB) @@ -763,7 +530,7 @@ class Page0Registers(PagedRegisterBase): # Negative dB range return -((raw_val - 0x24) * 0.5) - def set_int2_source(self, headset_detect=False, button_press=False, dac_drc=False, + def _set_int2_source(self, headset_detect=False, button_press=False, dac_drc=False, agc_noise=False, over_current=False, multiple_pulse=False): """Configure the INT2 interrupt sources. @@ -786,6 +553,155 @@ class Page0Registers(PagedRegisterBase): self._write_register(REG_INT2_CTRL, value) return True + def _set_codec_interface(self, format, data_len, bclk_out=False, wclk_out=False): + """Set the codec interface parameters.""" + value = (format & 0x03) << 6 + value |= (data_len & 0x03) << 4 + value |= (1 if bclk_out else 0) << 3 + value |= (1 if wclk_out else 0) << 2 + + self._write_register(REG_CODEC_IF_CTRL1, value) + return True + + def _configure_clocks_for_sample_rate(self, mclk_freq, sample_rate, bit_depth): + """Configure clock settings for the specified sample rate. + + :param mclk_freq: The master clock frequency in Hz + :param sample_rate: The desired sample rate in Hz + :param bit_depth: The bit depth (16, 20, 24, or 32) + :return: True if successful, False otherwise + """ + # Map bit depth to register value + if bit_depth == 16: + data_len = DATA_LEN_16 + elif bit_depth == 20: + data_len = DATA_LEN_20 + elif bit_depth == 24: + data_len = DATA_LEN_24 + else: # Default to 32-bit for any other value + data_len = DATA_LEN_32 + + # First check if we can directly divide MCLK to get the sample rate + if mclk_freq % (128 * sample_rate) == 0: + # Simple case: configure without PLL + div_ratio = mclk_freq // (128 * sample_rate) + + # Set clock inputs to use MCLK directly + self._set_bits(REG_CLOCK_MUX1, 0x03, 0, 0b00) # CODEC_CLKIN = MCLK + + # Turn off PLL + self._set_bits(REG_PLL_PROG_PR, 0x01, 7, 0) # Power down PLL + + # Configure dividers + if div_ratio <= 128: + # Configure NDAC + self._write_register(REG_NDAC, 0x80 | (div_ratio & 0x7F)) # Enable with value + + # Configure MDAC + self._write_register(REG_MDAC, 0x81) # Enable with value 1 + + # Configure DOSR (128 is typical) + self._write_register(REG_DOSR_MSB, 0) + self._write_register(REG_DOSR_LSB, 128) + + # Set codec interface format + self._set_codec_interface(FORMAT_I2S, data_len) + return True + + # If direct division doesn't work, use PLL + # Lookup tables for common configurations + if mclk_freq == 12000000: + if sample_rate == 22050: + # Configuration for 22.05kHz from 12MHz + p, r, j, d = 1, 1, 7, 6144 + ndac = 8 # Double the divisor compared to 44.1kHz + mdac = 1 + dosr = 128 + elif sample_rate == 44100: + # Common configuration for 44.1kHz from 12MHz + p, r, j, d = 1, 1, 7, 6144 + ndac = 4 + mdac = 1 + dosr = 128 + elif sample_rate == 48000: + # Common configuration for 48kHz from 12MHz + p, r, j, d = 1, 1, 8, 0 + ndac = 4 + mdac = 1 + dosr = 128 + elif sample_rate == 96000: + # Configuration for 96kHz from 12MHz + p, r, j, d = 1, 1, 8, 0 + ndac = 2 + mdac = 1 + dosr = 128 + else: + # Not a supported sample rate for 12MHz MCLK + return False + elif mclk_freq == 24000000: + if sample_rate == 44100: + # Configuration for 44.1kHz from 24MHz + p, r, j, d = 1, 2, 7, 6144 + ndac = 4 + mdac = 1 + dosr = 128 + elif sample_rate == 48000: + # Configuration for 48kHz from 24MHz + p, r, j, d = 1, 2, 8, 0 + ndac = 4 + mdac = 1 + dosr = 128 + elif sample_rate == 96000: + # Configuration for 96kHz from 24MHz + p, r, j, d = 1, 2, 8, 0 + ndac = 2 + mdac = 1 + dosr = 128 + else: + # Not a supported sample rate for 24MHz MCLK + return False + else: + # Unsupported MCLK frequency + return False + + # Configure PLL + # Set clock sources + self._set_bits(REG_CLOCK_MUX1, 0x03, 2, 0b00) # PLL_CLKIN = MCLK + self._set_bits(REG_CLOCK_MUX1, 0x03, 0, 0b11) # CODEC_CLKIN = PLL + + # Set P and R values + pr_value = ((p & 0x07) << 4) | (r & 0x0F) + self._write_register(REG_PLL_PROG_PR, pr_value & 0x7F) # Don't enable PLL yet + + # Set J value + self._write_register(REG_PLL_PROG_J, j & 0x3F) + + # Set D value (14 bits) + self._write_register(REG_PLL_PROG_D_MSB, (d >> 8) & 0xFF) + self._write_register(REG_PLL_PROG_D_LSB, d & 0xFF) + + # Configure dividers + # NDAC + self._write_register(REG_NDAC, 0x80 | (ndac & 0x7F)) # Enable with value + + # MDAC + self._write_register(REG_MDAC, 0x80 | (mdac & 0x7F)) # Enable with value + + # DOSR + self._write_register(REG_DOSR_MSB, (dosr >> 8) & 0xFF) + self._write_register(REG_DOSR_LSB, dosr & 0xFF) + + # Set codec interface format + self._set_codec_interface(FORMAT_I2S, data_len) + + # Power up PLL + self._set_bits(REG_PLL_PROG_PR, 0x01, 7, 1) # Power up PLL + + # Wait for PLL to lock + time.sleep(0.01) # 10ms should be enough for PLL to stabilize + + return True + class Page1Registers(PagedRegisterBase): """Page 1 registers containing analog output settings, HP/SPK controls, etc.""" @@ -797,15 +713,15 @@ class Page1Registers(PagedRegisterBase): """ super().__init__(i2c_device, 1) - def get_speaker_enabled(self): + def _get_speaker_enabled(self): """Check if speaker is enabled.""" return bool(self._get_bits(REG_SPK_AMP, 0x01, 7)) - def set_speaker_enabled(self, enable): + def _set_speaker_enabled(self, enable): """Enable or disable the Class-D speaker amplifier.""" return self._set_bits(REG_SPK_AMP, 0x01, 7, 1 if enable else 0) - def configure_headphone_driver(self, left_powered, right_powered, + def _configure_headphone_driver(self, left_powered, right_powered, common=HP_COMMON_1_35V, power_down_on_scd=False): """Configure headphone driver settings.""" value = 0x04 # bit 2 must be 1 @@ -817,7 +733,7 @@ class Page1Registers(PagedRegisterBase): self._write_register(REG_HP_DRIVERS, value) return True - def configure_analog_inputs(self, left_dac=DAC_ROUTE_NONE, right_dac=DAC_ROUTE_NONE, + def _configure_analog_inputs(self, left_dac=DAC_ROUTE_NONE, right_dac=DAC_ROUTE_NONE, left_ain1=False, left_ain2=False, right_ain2=False, hpl_routed_to_hpr=False): """Configure DAC and analog input routing.""" @@ -832,7 +748,7 @@ class Page1Registers(PagedRegisterBase): self._write_register(REG_OUT_ROUTING, value) return True - def set_hpl_volume(self, route_enabled, gain=0x7F): + def _set_hpl_volume(self, route_enabled, gain=0x7F): """Set HPL analog volume control.""" if gain > 0x7F: gain = 0x7F @@ -841,7 +757,7 @@ class Page1Registers(PagedRegisterBase): self._write_register(REG_HPL_VOL, value) return True - def set_hpr_volume(self, route_enabled, gain=0x7F): + def _set_hpr_volume(self, route_enabled, gain=0x7F): """Set HPR analog volume control.""" if gain > 0x7F: gain = 0x7F @@ -850,7 +766,7 @@ class Page1Registers(PagedRegisterBase): self._write_register(REG_HPR_VOL, value) return True - def set_spk_volume(self, route_enabled, gain=0x7F): + def _set_spk_volume(self, route_enabled, gain=0x7F): """Set Speaker analog volume control.""" if gain > 0x7F: gain = 0x7F @@ -859,7 +775,7 @@ class Page1Registers(PagedRegisterBase): self._write_register(REG_SPK_VOL, value) return True - def configure_hpl_pga(self, gain_db=0, unmute=True): + def _configure_hpl_pga(self, gain_db=0, unmute=True): """Configure HPL driver PGA settings.""" if gain_db > 9: return False @@ -870,7 +786,7 @@ class Page1Registers(PagedRegisterBase): self._write_register(REG_HPL_DRIVER, value) return True - def configure_hpr_pga(self, gain_db=0, unmute=True): + def _configure_hpr_pga(self, gain_db=0, unmute=True): """Configure HPR driver PGA settings.""" if gain_db > 9: return False @@ -881,7 +797,7 @@ class Page1Registers(PagedRegisterBase): self._write_register(REG_HPR_DRIVER, value) return True - def configure_spk_pga(self, gain=SPK_GAIN_6DB, unmute=True): + def _configure_spk_pga(self, gain=SPK_GAIN_6DB, unmute=True): """Configure Speaker driver settings.""" value = (gain & 0x03) << 3 if unmute: value |= (1 << 2) @@ -889,44 +805,42 @@ class Page1Registers(PagedRegisterBase): self._write_register(REG_SPK_DRIVER, value) return True - def is_headphone_shorted(self): + def _is_headphone_shorted(self): """Check if headphone short circuit is detected. :return: True if short circuit detected, False if not """ - # TODO: Need to confirm the exact register and bit for headphone short detection - # This is a placeholder based on available information return False - def is_speaker_shorted(self): + def _is_speaker_shorted(self): """Check if speaker short circuit is detected. :return: True if short circuit detected, False if not """ return bool(self._get_bits(REG_SPK_AMP, 0x01, 0)) - def is_hpl_gain_applied(self): + def _is_hpl_gain_applied(self): """Check if all programmed gains have been applied to HPL. :return: True if gains applied, False if still ramping """ return bool(self._get_bits(REG_HPL_DRIVER, 0x01, 0)) - def is_hpr_gain_applied(self): + def _is_hpr_gain_applied(self): """Check if all programmed gains have been applied to HPR. :return: True if gains applied, False if still ramping """ return bool(self._get_bits(REG_HPR_DRIVER, 0x01, 0)) - def is_spk_gain_applied(self): + def _is_spk_gain_applied(self): """Check if all programmed gains have been applied to Speaker. :return: True if gains applied, False if still ramping """ return bool(self._get_bits(REG_SPK_DRIVER, 0x01, 0)) - def reset_speaker_on_scd(self, reset): + def _reset_speaker_on_scd(self, reset): """Configure speaker reset behavior on short circuit detection. :param reset: True to reset speaker on short circuit, False to remain unchanged @@ -935,7 +849,7 @@ class Page1Registers(PagedRegisterBase): # Register is inverse of parameter (0 = reset, 1 = no reset) return self._set_bits(REG_HP_SPK_ERR_CTL, 0x01, 1, 0 if reset else 1) - def reset_headphone_on_scd(self, reset): + def _reset_headphone_on_scd(self, reset): """Configure headphone reset behavior on short circuit detection. :param reset: True to reset headphone on short circuit, False to remain unchanged @@ -944,7 +858,7 @@ class Page1Registers(PagedRegisterBase): # Register is inverse of parameter (0 = reset, 1 = no reset) return self._set_bits(REG_HP_SPK_ERR_CTL, 0x01, 0, 0 if reset else 1) - def configure_headphone_pop(self, wait_for_powerdown=True, powerup_time=0x07, ramp_time=0x03): + def _configure_headphone_pop(self, wait_for_powerdown=True, powerup_time=0x07, ramp_time=0x03): """Configure headphone pop removal settings. :param wait_for_powerdown: Wait for amp powerdown before DAC powerdown @@ -959,7 +873,7 @@ class Page1Registers(PagedRegisterBase): self._write_register(REG_HP_POP, value) return True - def set_speaker_wait_time(self, wait_time=0): + def _set_speaker_wait_time(self, wait_time=0): """Set speaker power-up wait time. :param wait_time: Speaker power-up wait duration (0-7) @@ -967,7 +881,7 @@ class Page1Registers(PagedRegisterBase): """ return self._set_bits(REG_PGA_RAMP, 0x07, 4, wait_time) - def headphone_lineout(self, left, right): + def _headphone_lineout(self, left, right): """Configure headphone outputs as line-out. :param left: Configure left channel as line-out @@ -981,7 +895,7 @@ class Page1Registers(PagedRegisterBase): self._write_register(REG_HP_DRIVER_CTRL, value) return True - def config_mic_bias(self, power_down=False, always_on=False, voltage=0): + def _config_mic_bias(self, power_down=False, always_on=False, voltage=0): """Configure MICBIAS settings.""" value = (1 if power_down else 0) << 7 value |= (1 if always_on else 0) << 3 @@ -990,7 +904,7 @@ class Page1Registers(PagedRegisterBase): self._write_register(REG_MICBIAS, value) # Using constant instead of 0x2E return True - def set_input_common_mode(self, ain1_cm, ain2_cm): + def _set_input_common_mode(self, ain1_cm, ain2_cm): """Set analog input common mode connections.""" value = 0 if ain1_cm: value |= (1 << 7) @@ -1009,7 +923,7 @@ class Page3Registers(PagedRegisterBase): """ super().__init__(i2c_device, 3) - def config_delay_divider(self, use_mclk=True, divider=1): + def _config_delay_divider(self, use_mclk=True, divider=1): """Configure programmable delay timer clock source and divider.""" value = (1 if use_mclk else 0) << 7 value |= (divider & 0x7F) @@ -1033,6 +947,11 @@ class TLV320DAC3100: self._page0 = Page0Registers(self._device) self._page1 = Page1Registers(self._device) self._page3 = Page3Registers(self._device) + + # Initialize configuration tracking variables + self._sample_rate = 44100 # Default + self._bit_depth = 16 # Default + self._mclk_freq = 12000000 # Default # Reset the device if not self.reset(): @@ -1042,325 +961,356 @@ class TLV320DAC3100: def reset(self): """Reset the device.""" - return self._page0.reset() + return self._page0._reset() @property def overtemperature(self): """Check if the chip is overheating.""" - return self._page0.is_overtemperature() - - # PLL and Clock properties - - @property - def pll_clock_input(self): - """Get the PLL clock input source.""" - return self._page0.get_pll_clock_input() - - @pll_clock_input.setter - def pll_clock_input(self, clkin): - """Set the PLL clock input source.""" - self._page0.set_pll_clock_input(clkin) - - @property - def codec_clock_input(self): - """Get the CODEC clock input source.""" - return self._page0.get_codec_clock_input() - - @codec_clock_input.setter - def codec_clock_input(self, clkin): - """Set the CODEC clock input source.""" - self._page0.set_codec_clock_input(clkin) - - @property - def pll_power(self): - """Get the PLL power state.""" - return self._page0.get_pll_power() - - @pll_power.setter - def pll_power(self, on): - """Set the PLL power state.""" - self._page0.set_pll_power(on) - - # Speaker property - - @property - def speaker_enabled(self): - """Get the speaker amplifier state.""" - return self._page1.get_speaker_enabled() - - @speaker_enabled.setter - def speaker_enabled(self, enable): - """Enable or disable the speaker amplifier.""" - self._page1.set_speaker_enabled(enable) + return self._page0._is_overtemperature() # Method-based API for complex operations - def set_pll_values(self, p, r, j, d): - """Set the PLL P, R, J, and D values.""" - return self._page0.set_pll_values(p, r, j, d) - - def power_pll(self, on): - """Set the PLL power state.""" - return self._page0.set_pll_power(on) - - def set_ndac(self, enable, val): - """Set the NDAC value and enable/disable.""" - return self._page0.set_ndac(enable, val) - - def set_mdac(self, enable, val): - """Set the MDAC value and enable/disable.""" - return self._page0.set_mdac(enable, val) - - def set_codec_interface(self, format, data_len, bclk_out=False, wclk_out=False): - """Set the codec interface parameters.""" - return self._page0.set_codec_interface(format, data_len, bclk_out, wclk_out) - def set_headset_detect(self, enable, detect_debounce=0, button_debounce=0): """Configure headset detection settings.""" - return self._page0.set_headset_detect(enable, detect_debounce, button_debounce) + return self._page0._set_headset_detect(enable, detect_debounce, button_debounce) - def set_int1_source(self, headset_detect, button_press, dac_drc, + def int1_source(self, headset_detect, button_press, dac_drc, agc_noise, over_current, multiple_pulse): """Configure the INT1 interrupt sources.""" - return self._page0.set_int1_source(headset_detect, button_press, dac_drc, + return self._page0._set_int1_source(headset_detect, button_press, dac_drc, agc_noise, over_current, multiple_pulse) - - def set_gpio1_mode(self, mode): - """Set the GPIO1 pin mode.""" - return self._page0.set_gpio1_mode(mode) - def set_dac_data_path(self, left_dac_on, right_dac_on, - left_path=DAC_PATH_NORMAL, - right_path=DAC_PATH_NORMAL, - volume_step=VOLUME_STEP_1SAMPLE): - """Configure the DAC data path settings.""" - return self._page0.set_dac_data_path(left_dac_on, right_dac_on, - left_path, right_path, volume_step) + @property + def left_dac(self): + """Get the left DAC enabled status.""" + return self._page0._get_dac_data_path()['left_dac_on'] + + @left_dac.setter + def left_dac(self, enabled): + """Set the left DAC enabled status.""" + current = self._page0._get_dac_data_path() + self._page0._set_dac_data_path( + enabled, + current['right_dac_on'], + current['left_path'], + current['right_path'], + current['volume_step'] + ) + + @property + def right_dac(self): + """Get the right DAC enabled status.""" + return self._page0._get_dac_data_path()['right_dac_on'] + + @right_dac.setter + def right_dac(self, enabled): + """Set the right DAC enabled status.""" + current = self._page0._get_dac_data_path() + self._page0._set_dac_data_path( + current['left_dac_on'], + enabled, + current['left_path'], + current['right_path'], + current['volume_step'] + ) + + @property + def left_dac_path(self): + """Get the left DAC path setting.""" + return self._page0._get_dac_data_path()['left_path'] + + @left_dac_path.setter + def left_dac_path(self, path): + """Set the left DAC path.""" + current = self._page0._get_dac_data_path() + self._page0._set_dac_data_path( + current['left_dac_on'], + current['right_dac_on'], + path, + current['right_path'], + current['volume_step'] + ) + + @property + def right_dac_path(self): + """Get the right DAC path setting.""" + return self._page0._get_dac_data_path()['right_path'] + + @right_dac_path.setter + def right_dac_path(self, path): + """Set the right DAC path.""" + current = self._page0._get_dac_data_path() + self._page0._set_dac_data_path( + current['left_dac_on'], + current['right_dac_on'], + current['left_path'], + path, + current['volume_step'] + ) + + @property + def dac_volume_step(self): + """DAC volume step setting.""" + return self._page0._get_dac_data_path()['volume_step'] + + @dac_volume_step.setter + def dac_volume_step(self, step): + current = self._page0._get_dac_data_path() + self._page0._set_dac_data_path( + current['left_dac_on'], + current['right_dac_on'], + current['left_path'], + current['right_path'], + step + ) def configure_analog_inputs(self, left_dac=DAC_ROUTE_NONE, right_dac=DAC_ROUTE_NONE, left_ain1=False, left_ain2=False, right_ain2=False, hpl_routed_to_hpr=False): """Configure DAC and analog input routing.""" - return self._page1.configure_analog_inputs(left_dac, right_dac, + return self._page1._configure_analog_inputs(left_dac, right_dac, left_ain1, left_ain2, right_ain2, hpl_routed_to_hpr) - def set_dac_volume_control(self, left_mute, right_mute, control=VOL_INDEPENDENT): - """Configure the DAC volume control settings.""" - return self._page0.set_dac_volume_control(left_mute, right_mute, control) - - def set_channel_volume(self, right_channel, db): - """Set DAC channel volume in dB.""" - return self._page0.set_channel_volume(right_channel, db) - - def configure_headphone_driver(self, left_powered, right_powered, - common=HP_COMMON_1_35V, power_down_on_scd=False): - """Configure headphone driver settings.""" - return self._page1.configure_headphone_driver(left_powered, right_powered, - common, power_down_on_scd) - - def set_hpl_volume(self, route_enabled, gain=0x7F): - """Set HPL analog volume control.""" - return self._page1.set_hpl_volume(route_enabled, gain) - - def configure_hpl_pga(self, gain_db=0, unmute=True): - """Configure HPL driver PGA settings.""" - return self._page1.configure_hpl_pga(gain_db, unmute) - - def set_hpr_volume(self, route_enabled, gain=0x7F): - """Set HPR analog volume control.""" - return self._page1.set_hpr_volume(route_enabled, gain) - - def configure_hpr_pga(self, gain_db=0, unmute=True): - """Configure HPR driver PGA settings.""" - return self._page1.configure_hpr_pga(gain_db, unmute) - - def enable_speaker(self, enable): - """Enable or disable the Class-D speaker amplifier.""" - return self._page1.set_speaker_enabled(enable) - - def configure_spk_pga(self, gain=SPK_GAIN_6DB, unmute=True): - """Configure Speaker driver settings.""" - return self._page1.configure_spk_pga(gain, unmute) - - def set_spk_volume(self, route_enabled, gain=0x7F): - """Set Speaker analog volume control.""" - return self._page1.set_spk_volume(route_enabled, gain) + @property + def left_dac_mute(self): + """Get the left DAC mute status.""" + return self._page0._get_dac_volume_control()['left_mute'] + + @left_dac_mute.setter + def left_dac_mute(self, mute): + """Set the left DAC mute status.""" + current = self._page0._get_dac_volume_control() + self._page0._set_dac_volume_control(mute, current['right_mute'], current['control']) + + @property + def right_dac_mute(self): + """Get the right DAC mute status.""" + return self._page0._get_dac_volume_control()['right_mute'] + + @right_dac_mute.setter + def right_dac_mute(self, mute): + """Set the right DAC mute status.""" + current = self._page0._get_dac_volume_control() + self._page0._set_dac_volume_control(current['left_mute'], mute, current['control']) + + @property + def dac_volume_control_mode(self): + """DAC volume control mode.""" + return self._page0._get_dac_volume_control()['control'] + + @dac_volume_control_mode.setter + def dac_volume_control_mode(self, mode): + """Set the volume control mode.""" + current = self._page0._get_dac_volume_control() + self._page0._set_dac_volume_control(current['left_mute'], current['right_mute'], mode) @property - def clock_divider_input(self): - """Get the current clock divider input source.""" - return self._page0.get_clock_divider_input() + def left_channel_volume(self): + """Get left DAC channel volume in dB.""" + return self._page0._get_channel_volume(False) - @clock_divider_input.setter - def clock_divider_input(self, clkin): - """Set the clock divider input source.""" - self._page0.set_clock_divider_input(clkin) + @left_channel_volume.setter + def left_channel_volume(self, db): + """Set left DAC channel volume in dB.""" + self._page0._set_channel_volume(False, db) - def get_clkout_m(self): - """Get the CLKOUT M divider value and enabled state. - - :return: Tuple of (enabled, value) - """ - return self._page0.get_clkout_m() + @property + def right_channel_volume(self): + """Get right DAC channel volume in dB.""" + return self._page0._get_channel_volume(True) - def get_bclk_offset(self): - """Get the BCLK data slot offset. - - :return: Current offset value (0-255) - """ - return self._page0.get_bclk_offset() - - def get_bclk_n(self): - """Get the BCLK N divider value and enabled state. - - :return: Tuple of (enabled, value) - """ - return self._page0.get_bclk_n() + @right_channel_volume.setter + def right_channel_volume(self, db): + """Set right DAC channel volume in dB.""" + self._page0._set_channel_volume(True, db) - def get_pll_values(self): - """Get the current PLL P, R, J, and D values. - - :return: Tuple of (P, R, J, D) values - """ - return self._page0.get_pll_values() - - def get_ndac(self): - """Get the NDAC value and enabled state. - - :return: Tuple of (enabled, value) - """ - return self._page0.get_ndac() - - def get_mdac(self): - """Get the MDAC value and enabled state. - - :return: Tuple of (enabled, value) - """ - return self._page0.get_mdac() - - def get_dosr(self): - """Get the DOSR divider value. - - :return: Current DOSR value - """ - return self._page0.get_dosr() - - def set_dosr(self, val): - """Set the DOSR divider value. - - :param val: DOSR divider value (2-1024, except 1023) - :return: True if successful, False if failure - """ - return self._page0.set_dosr(val) + def manual_headphone_driver(self, left_powered, right_powered, + common=HP_COMMON_1_35V, power_down_on_scd=False): + """Configure headphone driver settings.""" + return self._page1._configure_headphone_driver(left_powered, right_powered, + common, power_down_on_scd) - def get_dac_flags(self): - """Get the DAC and output driver status flags. - - :return: Dictionary with status flags for various components - """ - return self._page0.get_dac_flags() - - def get_gpio1_input(self): - """Get the current GPIO1 input value. - - :return: Current GPIO1 input state (True/False) - """ - return self._page0.get_gpio1_input() - - def get_din_input(self): - """Get the current DIN input value. - - :return: Current DIN input state (True/False) - """ - return self._page0.get_din_input() - - def get_codec_interface(self): - """Get the current codec interface settings. - - :return: Dictionary with format, data_len, bclk_out, and wclk_out values - """ - return self._page0.get_codec_interface() - - def get_dac_data_path(self): - """Get the current DAC data path configuration. - - :return: Dictionary with DAC data path settings - """ - return self._page0.get_dac_data_path() - - def get_dac_volume_control(self): - """Get the current DAC volume control configuration. - - :return: Dictionary with volume control settings - """ - return self._page0.get_dac_volume_control() - - def get_channel_volume(self, right_channel): - """Get DAC channel volume in dB. - - :param right_channel: True for right channel, False for left channel - :return: Current volume in dB - """ - return self._page0.get_channel_volume(right_channel) + def manual_headphone_left_volume(self, route_enabled, gain=0x7F): + """Set HPL analog volume control.""" + return self._page1._set_hpl_volume(route_enabled, gain) - def is_headphone_shorted(self): - """Check if headphone short circuit is detected. - - :return: True if short circuit detected, False if not - """ - return self._page1.is_headphone_shorted() + def manual_headphone_right_volume(self, route_enabled, gain=0x7F): + """Set HPR analog volume control.""" + return self._page1._set_hpr_volume(route_enabled, gain) - def is_speaker_shorted(self): - """Check if speaker short circuit is detected. - - :return: True if short circuit detected, False if not - """ - return self._page1.is_speaker_shorted() + @property + def headphone_left_gain(self): + """Get the left headphone gain in dB.""" + reg_value = self._page1._read_register(REG_HPL_DRIVER) + return (reg_value >> 3) & 0x0F - def is_hpl_gain_applied(self): - """Check if all programmed gains have been applied to HPL. - - :return: True if gains applied, False if still ramping - """ - return self._page1.is_hpl_gain_applied() + @headphone_left_gain.setter + def headphone_left_gain(self, gain_db): + """Set the left headphone gain in dB.""" + unmute = not self.headphone_left_mute + self._page1._configure_hpl_pga(gain_db, unmute) - def is_hpr_gain_applied(self): - """Check if all programmed gains have been applied to HPR. - - :return: True if gains applied, False if still ramping - """ - return self._page1.is_hpr_gain_applied() + @property + def headphone_left_mute(self): + """Get the left headphone mute status.""" + reg_value = self._page1._read_register(REG_HPL_DRIVER) + return not bool(reg_value & (1 << 2)) # Inverse of unmute bit - def is_spk_gain_applied(self): - """Check if all programmed gains have been applied to Speaker. - - :return: True if gains applied, False if still ramping - """ - return self._page1.is_spk_gain_applied() + @headphone_left_mute.setter + def headphone_left_mute(self, mute): + """Set the left headphone mute status.""" + gain = self.headphone_left_gain + self._page1._configure_hpl_pga(gain, not mute) # Unmute is inverse of mute - def get_headset_status(self): + @property + def headphone_right_gain(self): + """Get the right headphone gain in dB.""" + reg_value = self._page1._read_register(REG_HPR_DRIVER) + return (reg_value >> 3) & 0x0F + + @headphone_right_gain.setter + def headphone_right_gain(self, gain_db): + """Set the right headphone gain in dB.""" + unmute = not self.headphone_right_mute + self._page1._configure_hpr_pga(gain_db, unmute) + + @property + def headphone_right_mute(self): + """Get the right headphone mute status.""" + reg_value = self._page1._read_register(REG_HPR_DRIVER) + return not bool(reg_value & (1 << 2)) # Inverse of unmute bit + + @headphone_right_mute.setter + def headphone_right_mute(self, mute): + """Set the right headphone mute status.""" + gain = self.headphone_right_gain + self._page1._configure_hpr_pga(gain, not mute) # Unmute is inverse of mute + + @property + def speaker_gain(self): + """Get the speaker gain setting.""" + reg_value = self._page1._read_register(REG_SPK_DRIVER) + return (reg_value >> 3) & 0x03 + + @speaker_gain.setter + def speaker_gain(self, gain): + """Set the speaker gain.""" + unmute = not self.speaker_mute + self._page1._configure_spk_pga(gain, unmute) + + @property + def speaker_mute(self): + """Get the speaker mute status.""" + reg_value = self._page1._read_register(REG_SPK_DRIVER) + return not bool(reg_value & (1 << 2)) # Inverse of unmute bit + + @speaker_mute.setter + def speaker_mute(self, mute): + """Set the speaker mute status.""" + gain = self.speaker_gain + self._page1._configure_spk_pga(gain, not mute) # Unmute is inverse of mute + + @property + def dac_flags(self): + """Get the DAC and output driver status flags.""" + return self._page0._get_dac_flags() + + @property + def gpio1_mode(self): + """Get the current GPIO1 pin mode. + + :return: The GPIO1 mode setting + """ + value = self._page0._read_register(REG_GPIO1_CTRL) + return (value >> 2) & 0x0F + + @gpio1_mode.setter + def gpio1_mode(self, mode): + """Set the GPIO1 pin mode. + + :param mode: One of the GPIO1_* mode constants + """ + self._page0._set_gpio1_mode(mode) + + @property + def din_input(self): + """Get the current DIN input value.""" + return self._page0._get_din_input() + + @property + def codec_interface(self): + """Get the current codec interface settings.""" + return self._page0._get_codec_interface() + + @property + def headphone_shorted(self): + """Check if headphone short circuit is detected.""" + return self._page1._is_headphone_shorted() + + @property + def speaker_shorted(self): + """Check if speaker short circuit is detected.""" + return self._page1._is_speaker_shorted() + + @property + def hpl_gain_applied(self): + """Check if all programmed gains have been applied to HPL.""" + return self._page1._is_hpl_gain_applied() + + @property + def hpr_gain_applied(self): + """Check if all programmed gains have been applied to HPR.""" + return self._page1._is_hpr_gain_applied() + + @property + def speaker_gain_applied(self): + """Check if all programmed gains have been applied to Speaker.""" + return self._page1._is_spk_gain_applied() + + @property + def headset_status(self): """Get current headset detection status. :return: Integer value representing headset status (0=none, 1=without mic, 3=with mic) """ - return self._page0.get_headset_status() + return self._page0._get_headset_status() + @property + def reset_speaker_on_scd(self): + """Get the speaker reset behavior on short circuit detection. + + :return: True if speaker resets on short circuit, False otherwise + """ + value = self._page1._read_register(REG_HP_SPK_ERR_CTL) + # Register bit is inverse of property (0 = reset, 1 = no reset) + return not bool((value >> 1) & 0x01) + + @reset_speaker_on_scd.setter def reset_speaker_on_scd(self, reset): """Configure speaker reset behavior on short circuit detection. :param reset: True to reset speaker on short circuit, False to remain unchanged - :return: True if successful """ - return self._page1.reset_speaker_on_scd(reset) + self._page1._reset_speaker_on_scd(reset) + @property + def reset_headphone_on_scd(self): + """Get the headphone reset behavior on short circuit detection. + + :return: True if headphone resets on short circuit, False otherwise + """ + value = self._page1._read_register(REG_HP_SPK_ERR_CTL) + # Register bit is inverse of property (0 = reset, 1 = no reset) + return not bool(value & 0x01) + + @reset_headphone_on_scd.setter def reset_headphone_on_scd(self, reset): """Configure headphone reset behavior on short circuit detection. :param reset: True to reset headphone on short circuit, False to remain unchanged - :return: True if successful """ - return self._page1.reset_headphone_on_scd(reset) + self._page1._reset_headphone_on_scd(reset) def configure_headphone_pop(self, wait_for_powerdown=True, powerup_time=0x07, ramp_time=0x03): """Configure headphone pop removal settings. @@ -1370,24 +1320,45 @@ class TLV320DAC3100: :param ramp_time: Driver ramp-up step time (0-3) :return: True if successful """ - return self._page1.configure_headphone_pop(wait_for_powerdown, powerup_time, ramp_time) + return self._page1._configure_headphone_pop(wait_for_powerdown, powerup_time, ramp_time) - def set_speaker_wait_time(self, wait_time=0): + @property + def speaker_wait_time(self): + """Get the current speaker power-up wait time. + + :return: The wait time setting (0-7) + """ + value = self._page1._read_register(REG_PGA_RAMP) + return (value >> 4) & 0x07 + + @speaker_wait_time.setter + def speaker_wait_time(self, wait_time): """Set speaker power-up wait time. :param wait_time: Speaker power-up wait duration (0-7) - :return: True if successful """ - return self._page1.set_speaker_wait_time(wait_time) + self._page1._set_speaker_wait_time(wait_time) - def headphone_lineout(self, left, right): + @property + def headphone_lineout(self): + """Get the current headphone line-out configuration. + + :return: True if both channels are configured as line-out, False otherwise + """ + # Read the register value + value = self._page1._read_register(REG_HP_DRIVER_CTRL) + left = bool(value & (1 << 2)) + right = bool(value & (1 << 1)) + # Return True only if both channels are configured as line-out + return left and right + + @headphone_lineout.setter + def headphone_lineout(self, enabled): """Configure headphone outputs as line-out. - :param left: Configure left channel as line-out - :param right: Configure right channel as line-out - :return: True if successful + :param enabled: True to configure both channels as line-out, False otherwise """ - return self._page1.headphone_lineout(left, right) + self._page1._headphone_lineout(enabled, enabled) def config_mic_bias(self, power_down=False, always_on=False, voltage=0): """Configure MICBIAS settings. @@ -1397,7 +1368,7 @@ class TLV320DAC3100: :param voltage: MICBIAS voltage setting (0-3) :return: True if successful """ - return self._page1.config_mic_bias(power_down, always_on, voltage) + return self._page1._config_mic_bias(power_down, always_on, voltage) def set_input_common_mode(self, ain1_cm, ain2_cm): """Set analog input common mode connections. @@ -1406,7 +1377,7 @@ class TLV320DAC3100: :param ain2_cm: Connect AIN2 to common mode when unused :return: True if successful """ - return self._page1.set_input_common_mode(ain1_cm, ain2_cm) + return self._page1._set_input_common_mode(ain1_cm, ain2_cm) def config_delay_divider(self, use_mclk=True, divider=1): """Configure programmable delay timer clock source and divider. @@ -1415,27 +1386,92 @@ class TLV320DAC3100: :param divider: Clock divider (1-127, or 0 for 128) :return: True if successful """ - return self._page3.config_delay_divider(use_mclk, divider) + return self._page3._config_delay_divider(use_mclk, divider) - def config_vol_adc(self, pin_control=False, use_mclk=False, hysteresis=0, rate=0): - """Configure the Volume/MicDet pin ADC. - - :param pin_control: Enable pin control of DAC volume - :param use_mclk: Use MCLK instead of internal RC oscillator - :param hysteresis: ADC hysteresis setting (0-2) - :param rate: ADC sampling rate (0-7) - :return: True if successful - """ - return self._page0.config_vol_adc(pin_control, use_mclk, hysteresis, rate) + @property + def vol_adc_pin_control(self): + """Get the volume ADC pin control status.""" + reg_value = self._page0._read_register(REG_VOL_ADC_CTRL) + return bool(reg_value & (1 << 7)) - def read_vol_adc_db(self): - """Read the current volume from the Volume ADC in dB. - - :return: Current volume in dB (+18 to -63 dB) - """ - return self._page0.read_vol_adc_db() + @vol_adc_pin_control.setter + def vol_adc_pin_control(self, enabled): + """Enable or disable volume ADC pin control.""" + current_config = self._get_vol_adc_config() + self._page0._config_vol_adc( + enabled, + current_config['use_mclk'], + current_config['hysteresis'], + current_config['rate'] + ) - def set_int2_source(self, headset_detect=False, button_press=False, dac_drc=False, + @property + def vol_adc_use_mclk(self): + """Get the volume ADC use MCLK status.""" + reg_value = self._page0._read_register(REG_VOL_ADC_CTRL) + return bool(reg_value & (1 << 6)) + + @vol_adc_use_mclk.setter + def vol_adc_use_mclk(self, use_mclk): + """Set whether to use MCLK for volume ADC.""" + current_config = self._get_vol_adc_config() + self._page0._config_vol_adc( + current_config['pin_control'], + use_mclk, + current_config['hysteresis'], + current_config['rate'] + ) + + @property + def vol_adc_hysteresis(self): + """Get the volume ADC hysteresis setting.""" + reg_value = self._page0._read_register(REG_VOL_ADC_CTRL) + return (reg_value >> 4) & 0x03 + + @vol_adc_hysteresis.setter + def vol_adc_hysteresis(self, hysteresis): + """Set the volume ADC hysteresis.""" + current_config = self._get_vol_adc_config() + self._page0._config_vol_adc( + current_config['pin_control'], + current_config['use_mclk'], + hysteresis, + current_config['rate'] + ) + + @property + def vol_adc_rate(self): + """Get the volume ADC sampling rate.""" + reg_value = self._page0._read_register(REG_VOL_ADC_CTRL) + return reg_value & 0x07 + + @vol_adc_rate.setter + def vol_adc_rate(self, rate): + """Set the volume ADC sampling rate.""" + current_config = self._get_vol_adc_config() + self._page0._config_vol_adc( + current_config['pin_control'], + current_config['use_mclk'], + current_config['hysteresis'], + rate + ) + + def _get_vol_adc_config(self): + """Helper method to get the current volume ADC configuration.""" + reg_value = self._page0._read_register(REG_VOL_ADC_CTRL) + return { + 'pin_control': bool(reg_value & (1 << 7)), + 'use_mclk': bool(reg_value & (1 << 6)), + 'hysteresis': (reg_value >> 4) & 0x03, + 'rate': reg_value & 0x07 + } + + @property + def vol_adc_db(self): + """Read the current volume from the Volume ADC in dB.""" + return self._page0._read_vol_adc_db() + + def int2_sources(self, headset_detect=False, button_press=False, dac_drc=False, agc_noise=False, over_current=False, multiple_pulse=False): """Configure the INT2 interrupt sources. @@ -1447,5 +1483,249 @@ class TLV320DAC3100: :param multiple_pulse: If true, INT2 generates multiple pulses until flag read :return: True if successful """ - return self._page0.set_int2_source(headset_detect, button_press, dac_drc, - agc_noise, over_current, multiple_pulse) \ No newline at end of file + return self._page0._set_int2_source(headset_detect, button_press, dac_drc, + agc_noise, over_current, multiple_pulse) + + def configure_clocks(self, mclk_freq, sample_rate, bit_depth=16): + """Configure the TLV320DAC3100 clock settings. + + This function configures all necessary clock settings including PLL, dividers, + and interface settings to achieve the requested sample rate with the provided + master clock frequency. + + :param mclk_freq: The master clock frequency in Hz (e.g., 12000000 for 12MHz) + :param sample_rate: The desired sample rate in Hz (e.g., 44100, 48000) + :param bit_depth: The bit depth (16, 20, 24, or 32) + :return: True if successful, False otherwise + """ + # Store these values for later retrieval + self._mclk_freq = mclk_freq + self._sample_rate = sample_rate + self._bit_depth = bit_depth + + return self._page0._configure_clocks_for_sample_rate(mclk_freq, sample_rate, bit_depth) + + @property + def audio_preset(self): + """Get the current audio preset based on sample rate and bit depth. + + :return: One of the PRESET_* constants, or None if no matching preset + """ + if self._sample_rate == 22050 and self._bit_depth == 16: + return PRESET_MID_QUALITY + elif self._sample_rate == 44100 and self._bit_depth == 16: + return PRESET_CD_QUALITY + elif self._sample_rate == 48000 and self._bit_depth == 24: + return PRESET_DVD_QUALITY + elif self._sample_rate == 96000 and self._bit_depth == 24: + return PRESET_HIRES_QUALITY + else: + return None + + @audio_preset.setter + def audio_preset(self, preset): + """Configure the DAC for common audio presets. + + :param preset: One of the PRESET_* constants + """ + if preset == PRESET_MID_QUALITY: + # CircuitPython synth: 22.05kHz, 16-bit + self._sample_rate = 22050 + self._bit_depth = 16 + self.configure_clocks(12000000, 22050, 16) + elif preset == PRESET_CD_QUALITY: + # CD quality: 44.1kHz, 16-bit + self._sample_rate = 44100 + self._bit_depth = 16 + self.configure_clocks(12000000, 44100, 16) + elif preset == PRESET_DVD_QUALITY: + # DVD quality: 48kHz, 24-bit + self._sample_rate = 48000 + self._bit_depth = 24 + self.configure_clocks(12000000, 48000, 24) + elif preset == PRESET_HIRES_QUALITY: + # High-res audio: 96kHz, 24-bit + self._sample_rate = 96000 + self._bit_depth = 24 + self.configure_clocks(12000000, 96000, 24) + + @property + def headphone_output(self): + """Get headphone output state (True if either left or right channel is powered).""" + hp_drivers = self._page1._read_register(REG_HP_DRIVERS) + left_powered = bool(hp_drivers & (1 << 7)) + right_powered = bool(hp_drivers & (1 << 6)) + return left_powered or right_powered + + @headphone_output.setter + def headphone_output(self, enabled): + """Enable or disable headphone outputs.""" + if enabled: + # Configure headphone drivers - power both channels with common mode voltage + self._page1._configure_headphone_driver( + left_powered=True, + right_powered=True, + common=HP_COMMON_1_65V # Try a higher common-mode voltage + ) + + # Make sure HP driver registers are properly configured + self._page1._configure_hpl_pga(gain_db=0, unmute=True) + self._page1._configure_hpr_pga(gain_db=0, unmute=True) + + # Route DAC to headphone outputs + self._page1._configure_analog_inputs( + left_dac=DAC_ROUTE_HP, + right_dac=DAC_ROUTE_HP + ) + + # Enable DAC data path + self._page0._set_dac_data_path( + left_dac_on=True, + right_dac_on=True, + left_path=DAC_PATH_NORMAL, + right_path=DAC_PATH_NORMAL + ) + + # Set reasonable initial volume if not already set + self._page1._set_hpl_volume(route_enabled=True, gain=50) # Use a higher gain value + self._page1._set_hpr_volume(route_enabled=True, gain=50) # Use a higher gain value + + # Ensure DAC volume control is properly set + self._page0._set_dac_volume_control(False, False, VOL_INDEPENDENT) + + # Ensure adequate DAC volume + self._page0._set_channel_volume(False, 0) # Left channel at 0dB + self._page0._set_channel_volume(True, 0) # Right channel at 0dB + else: + # Power down headphone drivers + self._page1._configure_headphone_driver(left_powered=False, right_powered=False) + + # Unroute DAC from headphone outputs + self._page1._configure_analog_inputs( + left_dac=DAC_ROUTE_NONE, + right_dac=DAC_ROUTE_NONE + ) + + @property + def speaker_output(self): + """Get speaker output state.""" + return self._page1._get_speaker_enabled() + + @speaker_output.setter + def speaker_output(self, enabled): + """Enable or disable speaker output. + + This is a high-level function that: + 1. Powers on/off the speaker amplifier + 2. Sets appropriate volume and PGA settings when enabled + + :param enabled: True to enable speaker, False to disable + """ + if enabled: + # Enable speaker amplifier + self._page1._set_speaker_enabled(True) + + # Route DAC to speaker (via mixer) + self._page1._configure_analog_inputs( + left_dac=DAC_ROUTE_MIXER, + right_dac=DAC_ROUTE_MIXER + ) + + # Enable DAC data path if not already enabled + self._page0._set_dac_data_path( + left_dac_on=True, + right_dac_on=True + ) + + # Set reasonable initial volume and unmute + self._page1._set_spk_volume(route_enabled=True, gain=20) + self._page1._configure_spk_pga(gain=SPK_GAIN_6DB, unmute=True) + else: + # Disable speaker amplifier + self._page1._set_speaker_enabled(False) + + @property + def headphone_volume(self): + """Get the current headphone volume in dB. + + :return: The volume in dB (0 = max, -63.5 = min) + """ + # Since HPL and HPR might have different values, we'll average them + # Alternatively, you could decide to just return one channel + left_gain = self._page1._read_register(REG_HPL_VOL) & 0x7F + right_gain = self._page1._read_register(REG_HPR_VOL) & 0x7F + + avg_gain = (left_gain + right_gain) / 2 + # Convert from register value to dB + # 55 ≈ 0dB, 0 ≈ -63.5dB + db = (avg_gain - 55) / 1.14 + + return db + + @headphone_volume.setter + def headphone_volume(self, db): + """Set headphone volume in dB. + + :param db: Volume in dB (0 = max, -63.5 = min) + """ + # Convert from dB to register gain value (0-127) + # 0dB = ~55, -63.5dB = 0 + if db > 0: + db = 0 # Limit to 0dB to prevent distortion + + # Scale to analog volume value (approximate conversion) + gain = int(55 + (db * 1.14)) + + # Constrain to valid range + gain = max(0, min(gain, 127)) + + # Set volume for both channels + self._page1._set_hpl_volume(route_enabled=True, gain=gain) + self._page1._set_hpr_volume(route_enabled=True, gain=gain) + + @property + def speaker_volume(self): + """Get the current speaker volume in dB. + + :return: The volume in dB (0 = max, -63.5 = min) + """ + gain = self._page1._read_register(REG_SPK_VOL) & 0x7F + + # Convert from register value to dB + # 55 ≈ 0dB, 0 ≈ -63.5dB + db = (gain - 55) / 1.14 + + return db + + @speaker_volume.setter + def speaker_volume(self, db): + """Set speaker volume in dB. + + :param db: Volume in dB (0 = max, -63.5 = min) + """ + # Convert from dB to register gain value (similar to headphone) + if db > 0: + db = 0 # Limit to 0dB to prevent distortion + + # Scale to analog volume value (approximate conversion) + gain = int(55 + (db * 1.14)) + + # Constrain to valid range + gain = max(0, min(gain, 127)) + + self._page1._set_spk_volume(route_enabled=True, gain=gain) + + @property + def sample_rate(self): + """Get the configured sample rate in Hz.""" + return self._sample_rate + + @property + def bit_depth(self): + """Get the configured bit depth.""" + return self._bit_depth + + @property + def mclk_freq(self): + """Get the configured MCLK frequency in Hz.""" + return self._mclk_freq diff --git a/examples/tlv320_simpletest.py b/examples/tlv320_simpletest.py index 42772ff..55aac92 100644 --- a/examples/tlv320_simpletest.py +++ b/examples/tlv320_simpletest.py @@ -1,4 +1,44 @@ -# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries # SPDX-FileCopyrightText: Copyright (c) 2025 Liz Clark for Adafruit Industries -# -# SPDX-License-Identifier: Unlicense +# SPDX-License-Identifier: MIT + +import time +import array +import math +import audiocore +import board +import busio +import audiobusio +import adafruit_tlv320 + +i2c = busio.I2C(board.SCL, board.SDA) +dac = adafruit_tlv320.TLV320DAC3100(i2c) + +# use a preset +# (PRESET_MID_QUALITY, PRESET_CD_QUALITY, PRESET_DVD_QUALITY or PRESET_HIRES_QUALITY) +# dac.configure_audio_preset(adafruit_tlv320.PRESET_CD_QUALITY)) +# or set mclk, sample rate & bit depth manually +dac.configure_clocks(mclk_freq=12000000, sample_rate=22050, bit_depth=8) + +# use headphones +dac.headphone_output = True +dac.headphone_volume = -20 +# or use speaker +# dac.speaker_output = True +# dac.speaker_volume = -15 + +audio = audiobusio.I2SOut(board.I2S_BCLK, board.I2S_WS, board.I2S_DIN) +# Generate a sine wave +tone_volume = 0.5 +frequency = 440 +sample_rate = dac.sample_rate +length = sample_rate // frequency +sine_wave = array.array("h", [0] * length) +for i in range(length): + sine_wave[i] = int((math.sin(math.pi * 2 * i / length)) * tone_volume * (2 ** 15 - 1)) +sine_wave_sample = audiocore.RawSample(sine_wave, sample_rate=sample_rate) + +while True: + audio.stop() + time.sleep(1) + audio.play(sine_wave_sample, loop=True) + time.sleep(1) From f2f553390ca9afa6b955d51a18a489eb15db649b Mon Sep 17 00:00:00 2001 From: Liz Date: Tue, 18 Mar 2025 17:14:45 -0400 Subject: [PATCH 04/11] typing and ruff --- adafruit_tlv320.py | 1958 ++++++++++++++++++--------------- examples/tlv320_fulltest.py | 384 ++++--- examples/tlv320_simpletest.py | 24 +- examples/tlv320_sinetone.py | 107 -- 4 files changed, 1273 insertions(+), 1200 deletions(-) delete mode 100644 examples/tlv320_sinetone.py diff --git a/adafruit_tlv320.py b/adafruit_tlv320.py index 75692c7..305bb6c 100644 --- a/adafruit_tlv320.py +++ b/adafruit_tlv320.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025 Liz Clark for Adafruit Industries +# SPDX-FileCopyrightText: 2025 Liz Clark for Adafruit Industries # # SPDX-License-Identifier: MIT """ @@ -23,130 +23,120 @@ Implementation Notes https://circuitpython.org/downloads * Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice -* Adafruit's Register library: https://github.com/adafruit/Adafruit_CircuitPython_Register """ import time -from micropython import const + from adafruit_bus_device.i2c_device import I2CDevice +from micropython import const + +try: + from typing import Any, Dict, List, Literal, Optional, Tuple, TypedDict, Union, cast + + from busio import I2C +except ImportError: + pass __version__ = "0.0.0+auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_TLV320.git" # Register addresses -REG_PAGE_SELECT = const(0x00) -REG_RESET = const(0x01) -REG_OT_FLAG = const(0x03) -REG_CLOCK_MUX1 = const(0x04) -REG_PLL_PROG_PR = const(0x05) -REG_PLL_PROG_J = const(0x06) -REG_PLL_PROG_D_MSB = const(0x07) -REG_PLL_PROG_D_LSB = const(0x08) -REG_NDAC = const(0x0B) -REG_MDAC = const(0x0C) -REG_DOSR_MSB = const(0x0D) -REG_DOSR_LSB = const(0x0E) -REG_CLKOUT_MUX = const(0x19) -REG_CLKOUT_M = const(0x1A) -REG_CODEC_IF_CTRL1 = const(0x1B) -REG_DATA_SLOT_OFFSET = const(0x1C) -REG_BCLK_N = const(0x1E) -REG_DAC_FLAG = const(0x25) -REG_DAC_FLAG2 = const(0x26) -REG_INT1_CTRL = const(0x30) -REG_INT2_CTRL = const(0x31) -REG_GPIO1_CTRL = const(0x33) -REG_DIN_CTRL = const(0x36) -REG_DAC_PRB = const(0x3C) -REG_DAC_DATAPATH = const(0x3F) -REG_DAC_VOL_CTRL = const(0x40) -REG_DAC_LVOL = const(0x41) -REG_DAC_RVOL = const(0x42) -REG_HEADSET_DETECT = const(0x43) -REG_VOL_ADC_CTRL = const(0x74) # VOL/MICDET-Pin SAR ADC Control Register -REG_VOL_ADC_READ = const(0x75) # VOL/MICDET-Pin Gain Register +_PAGE_SELECT = const(0x00) +_RESET = const(0x01) +_OT_FLAG = const(0x03) +_CLOCK_MUX1 = const(0x04) +_PLL_PROG_PR = const(0x05) +_PLL_PROG_J = const(0x06) +_PLL_PROG_D_MSB = const(0x07) +_PLL_PROG_D_LSB = const(0x08) +_NDAC = const(0x0B) +_MDAC = const(0x0C) +_DOSR_MSB = const(0x0D) +_DOSR_LSB = const(0x0E) +_CODEC_IF_CTRL1 = const(0x1B) +_DAC_FLAG = const(0x25) +_DAC_FLAG2 = const(0x26) +_INT1_CTRL = const(0x30) +_INT2_CTRL = const(0x31) +_GPIO1_CTRL = const(0x33) +_DIN_CTRL = const(0x36) +_DAC_DATAPATH = const(0x3F) +_DAC_VOL_CTRL = const(0x40) +_DAC_LVOL = const(0x41) +_DAC_RVOL = const(0x42) +_HEADSET_DETECT = const(0x43) +_VOL_ADC_CTRL = const(0x74) # VOL/MICDET-Pin SAR ADC Control ister +_VOL_ADC_READ = const(0x75) # VOL/MICDET-Pin Gain Register # Page 1 registers -REG_HP_SPK_ERR_CTL = const(0x1E) -REG_HP_DRIVERS = const(0x1F) -REG_SPK_AMP = const(0x20) -REG_HP_POP = const(0x21) -REG_PGA_RAMP = const(0x22) -REG_OUT_ROUTING = const(0x23) -REG_HPL_VOL = const(0x24) -REG_HPR_VOL = const(0x25) -REG_SPK_VOL = const(0x26) -REG_HPL_DRIVER = const(0x28) -REG_HPR_DRIVER = const(0x29) -REG_SPK_DRIVER = const(0x2A) -REG_HP_DRIVER_CTRL = const(0x2C) -REG_MICBIAS = const(0x2E) # MICBIAS Configuration Register -REG_INPUT_CM = const(0x32) # Input Common Mode Settings Register +_HP_SPK_ERR_CTL = const(0x1E) +_HP_DRIVERS = const(0x1F) +_SPK_AMP = const(0x20) +_HP_POP = const(0x21) +_PGA_RAMP = const(0x22) +_OUT_ROUTING = const(0x23) +_HPL_VOL = const(0x24) +_HPR_VOL = const(0x25) +_SPK_VOL = const(0x26) +_HPL_DRIVER = const(0x28) +_HPR_DRIVER = const(0x29) +_SPK_DRIVER = const(0x2A) +_HP_DRIVER_CTRL = const(0x2C) +_MICBIAS = const(0x2E) # MICBIAS Configuration ister +_INPUT_CM = const(0x32) # Input Common Mode Settings Register # Page 3 registers -REG_TIMER_MCLK_DIV = const(0x10) # Timer Clock MCLK Divider Register +_TIMER_MCLK_DIV = const(0x10) # Timer Clock MCLK Divider Register # Default I2C address I2C_ADDR_DEFAULT = const(0x18) # Data format for I2S interface -FORMAT_I2S = const(0b00) # I2S format -FORMAT_DSP = const(0b01) # DSP format -FORMAT_RJF = const(0b10) # Right justified format -FORMAT_LJF = const(0b11) # Left justified format +FORMAT_I2S = const(0b00) # I2S format +FORMAT_DSP = const(0b01) # DSP format +FORMAT_RJF = const(0b10) # Right justified format +FORMAT_LJF = const(0b11) # Left justified format # Data length for I2S interface -DATA_LEN_16 = const(0b00) # 16 bits -DATA_LEN_20 = const(0b01) # 20 bits -DATA_LEN_24 = const(0b10) # 24 bits -DATA_LEN_32 = const(0b11) # 32 bits - -# Clock source options for PLL_CLKIN -PLL_CLKIN_MCLK = const(0b00) # MCLK pin is the source -PLL_CLKIN_BCLK = const(0b01) # BCLK pin is the source -PLL_CLKIN_GPIO1 = const(0b10) # GPIO1 pin is the source -PLL_CLKIN_DIN = const(0b11) # DIN pin is the source - -# Clock source options for CODEC_CLKIN -CODEC_CLKIN_MCLK = const(0b00) # MCLK pin is the source -CODEC_CLKIN_BCLK = const(0b01) # BCLK pin is the source -CODEC_CLKIN_GPIO1 = const(0b10) # GPIO1 pin is the source -CODEC_CLKIN_PLL = const(0b11) # PLL_CLK pin is the source +DATA_LEN_16 = const(0b00) # 16 bits +DATA_LEN_20 = const(0b01) # 20 bits +DATA_LEN_24 = const(0b10) # 24 bits +DATA_LEN_32 = const(0b11) # 32 bits # GPIO1 pin mode options -GPIO1_DISABLED = const(0b0000) # GPIO1 disabled (input and output buffers powered down) +GPIO1_DISABLED = const(0b0000) # GPIO1 disabled (input and output buffers powered down) GPIO1_INPUT_MODE = const(0b0001) # Input mode (secondary BCLK/WCLK/DIN input or ClockGen) -GPIO1_GPI = const(0b0010) # General-purpose input -GPIO1_GPO = const(0b0011) # General-purpose output -GPIO1_CLKOUT = const(0b0100) # CLKOUT output -GPIO1_INT1 = const(0b0101) # INT1 output -GPIO1_INT2 = const(0b0110) # INT2 output -GPIO1_BCLK_OUT = const(0b1000) # Secondary BCLK output for codec interface -GPIO1_WCLK_OUT = const(0b1001) # Secondary WCLK output for codec interface +GPIO1_GPI = const(0b0010) # General-purpose input +GPIO1_GPO = const(0b0011) # General-purpose output +GPIO1_CLKOUT = const(0b0100) # CLKOUT output +GPIO1_INT1 = const(0b0101) # INT1 output +GPIO1_INT2 = const(0b0110) # INT2 output +GPIO1_BCLK_OUT = const(0b1000) # Secondary BCLK output for codec interface +GPIO1_WCLK_OUT = const(0b1001) # Secondary WCLK output for codec interface # DAC channel data path options -DAC_PATH_OFF = const(0b00) # DAC data path off -DAC_PATH_NORMAL = const(0b01) # Normal path (L->L or R->R) +DAC_PATH_OFF = const(0b00) # DAC data path off +DAC_PATH_NORMAL = const(0b01) # Normal path (L->L or R->R) DAC_PATH_SWAPPED = const(0b10) # Swapped path (R->L or L->R) -DAC_PATH_MIXED = const(0b11) # Mixed L+R path +DAC_PATH_MIXED = const(0b11) # Mixed L+R path # DAC volume control soft stepping options -VOLUME_STEP_1SAMPLE = const(0b00) # One step per sample -VOLUME_STEP_2SAMPLE = const(0b01) # One step per two samples -VOLUME_STEP_DISABLED = const(0b10) # Soft stepping disabled +VOLUME_STEP_1SAMPLE = const(0b00) # One step per sample +VOLUME_STEP_2SAMPLE = const(0b01) # One step per two samples +VOLUME_STEP_DISABLED = const(0b10) # Soft stepping disabled # DAC volume control configuration options -VOL_INDEPENDENT = const(0b00) # Left and right channels independent +VOL_INDEPENDENT = const(0b00) # Left and right channels independent VOL_LEFT_TO_RIGHT = const(0b01) # Left follows right volume VOL_RIGHT_TO_LEFT = const(0b10) # Right follows left volume # DAC output routing options -DAC_ROUTE_NONE = const(0b00) # DAC not routed -DAC_ROUTE_MIXER = const(0b01) # DAC routed to mixer amplifier -DAC_ROUTE_HP = const(0b10) # DAC routed directly to HP driver +DAC_ROUTE_NONE = const(0b00) # DAC not routed +DAC_ROUTE_MIXER = const(0b01) # DAC routed to mixer amplifier +DAC_ROUTE_HP = const(0b10) # DAC routed directly to HP driver # Speaker amplifier gain options -SPK_GAIN_6DB = const(0b00) # 6 dB gain +SPK_GAIN_6DB = const(0b00) # 6 dB gain SPK_GAIN_12DB = const(0b01) # 12 dB gain SPK_GAIN_18DB = const(0b10) # 18 dB gain SPK_GAIN_24DB = const(0b11) # 24 dB gain @@ -158,88 +148,70 @@ HP_COMMON_1_65V = const(0b10) # Common-mode voltage 1.65V HP_COMMON_1_80V = const(0b11) # Common-mode voltage 1.80V # Headset detection debounce time options -DEBOUNCE_16MS = const(0b000) # 16ms debounce (2ms clock) -DEBOUNCE_32MS = const(0b001) # 32ms debounce (4ms clock) -DEBOUNCE_64MS = const(0b010) # 64ms debounce (8ms clock) -DEBOUNCE_128MS = const(0b011) # 128ms debounce (16ms clock) -DEBOUNCE_256MS = const(0b100) # 256ms debounce (32ms clock) -DEBOUNCE_512MS = const(0b101) # 512ms debounce (64ms clock) +DEBOUNCE_16MS = const(0b000) # 16ms debounce (2ms clock) +DEBOUNCE_32MS = const(0b001) # 32ms debounce (4ms clock) +DEBOUNCE_64MS = const(0b010) # 64ms debounce (8ms clock) +DEBOUNCE_128MS = const(0b011) # 128ms debounce (16ms clock) +DEBOUNCE_256MS = const(0b100) # 256ms debounce (32ms clock) +DEBOUNCE_512MS = const(0b101) # 512ms debounce (64ms clock) # Button press debounce time options -BTN_DEBOUNCE_0MS = const(0b00) # No debounce -BTN_DEBOUNCE_8MS = const(0b01) # 8ms debounce (1ms clock) +BTN_DEBOUNCE_0MS = const(0b00) # No debounce +BTN_DEBOUNCE_8MS = const(0b01) # 8ms debounce (1ms clock) BTN_DEBOUNCE_16MS = const(0b10) # 16ms debounce (2ms clock) BTN_DEBOUNCE_32MS = const(0b11) # 32ms debounce (4ms clock) -# Clock divider input source options -CDIV_CLKIN_MCLK = const(0b000) # MCLK (device pin) -CDIV_CLKIN_BCLK = const(0b001) # BCLK (device pin) -CDIV_CLKIN_DIN = const(0b010) # DIN (for systems where DAC is not required) -CDIV_CLKIN_PLL = const(0b011) # PLL_CLK (generated on-chip) -CDIV_CLKIN_DAC = const(0b100) # DAC_CLK (DAC DSP clock - generated on-chip) -CDIV_CLKIN_DAC_MOD = const(0b101) # DAC_MOD_CLK (generated on-chip) +# ruff: noqa: PLR0904, PLR0912, PLR0913, PLR0915, PLR0917 -# Audio presets -# Audio presets -PRESET_MID_QUALITY = const(0) # 22.05kHz, 16-bit (good for CircuitPython audio) -PRESET_CD_QUALITY = const(1) # 44.1kHz, 16-bit -PRESET_DVD_QUALITY = const(2) # 48kHz, 24-bit -PRESET_HIRES_QUALITY = const(3) # 96kHz, 24-bit class PagedRegisterBase: """Base class for paged register access.""" - + def __init__(self, i2c_device, page): """Initialize the paged register base. - + :param i2c_device: The I2C device :param page: The register page number """ self._device = i2c_device self._page = page self._buffer = bytearray(2) - + def _write_register(self, register, value): """Write a value to a register. - + :param register: The register address :param value: The value to write """ - # First set the page self._set_page() - - # Then write to the register self._buffer[0] = register self._buffer[1] = value with self._device as i2c: i2c.write(self._buffer) - + def _read_register(self, register): """Read a value from a register. - + :param register: The register address :return: The register value """ - # First set the page self._set_page() - - # Then read the register self._buffer[0] = register with self._device as i2c: i2c.write(self._buffer, end=1) i2c.readinto(self._buffer, start=0, end=1) return self._buffer[0] - + def _set_page(self): """Set the current register page.""" - self._buffer[0] = REG_PAGE_SELECT + self._buffer[0] = _PAGE_SELECT self._buffer[1] = self._page with self._device as i2c: i2c.write(self._buffer) - + def _get_bits(self, register, mask, shift): """Read specific bits from a register. - + :param register: The register address :param mask: The bit mask (after shifting) :param shift: The bit position (0 = LSB) @@ -247,147 +219,137 @@ class PagedRegisterBase: """ value = self._read_register(register) return (value >> shift) & mask - + def _set_bits(self, register, mask, shift, value): """Set specific bits in a register. - + :param register: The register address :param mask: The bit mask (after shifting) :param shift: The bit position (0 = LSB) :param value: The value to set - :return: True if successful """ reg_value = self._read_register(register) reg_value &= ~(mask << shift) # Clear the bits reg_value |= (value & mask) << shift # Set the new bits self._write_register(register, reg_value) - return True class Page0Registers(PagedRegisterBase): """Page 0 registers containing system configuration, clocking, etc.""" - + def __init__(self, i2c_device): """Initialize Page 0 registers. - + :param i2c_device: The I2C device """ super().__init__(i2c_device, 0) - + def _reset(self): """Perform a software _reset of the chip. - + :return: True if successful, False otherwise """ - # Set reset bit - self._write_register(REG_RESET, 1) - - # Wait for reset to complete - time.sleep(0.01) # 10ms delay - - # Reset bit should be 0 after reset completes - return self._read_register(REG_RESET) == 0 - + self._write_register(_RESET, 1) + time.sleep(0.01) + return self._read_register(_RESET) == 0 + def _is_overtemperature(self): """Check if the chip is in an over-temperature condition. - + :return: True if overtemp condition exists, False if temperature is OK """ - # Bit 1 of register 3 is the overtemp flag (0 = overtemp, 1 = normal) - return not ((self._read_register(REG_OT_FLAG) >> 1) & 0x01) - - def _set_int1_source(self, headset_detect, button_press, dac_drc, - agc_noise, over_current, multiple_pulse): + return not ((self._read_register(_OT_FLAG) >> 1) & 0x01) + + def _set_int1_source( + self, headset_detect, button_press, dac_drc, agc_noise, over_current, multiple_pulse + ): """Configure the INT1 interrupt sources.""" value = 0 - if headset_detect: value |= (1 << 7) - if button_press: value |= (1 << 6) - if dac_drc: value |= (1 << 5) - if over_current: value |= (1 << 3) - if agc_noise: value |= (1 << 2) - if multiple_pulse: value |= (1 << 0) - - self._write_register(REG_INT1_CTRL, value) - return True - + if headset_detect: + value |= 1 << 7 + if button_press: + value |= 1 << 6 + if dac_drc: + value |= 1 << 5 + if over_current: + value |= 1 << 3 + if agc_noise: + value |= 1 << 2 + if multiple_pulse: + value |= 1 << 0 + self._write_register(_INT1_CTRL, value) + def _set_gpio1_mode(self, mode): """Set the GPIO1 pin mode.""" - return self._set_bits(REG_GPIO1_CTRL, 0x0F, 2, mode) - + return self._set_bits(_GPIO1_CTRL, 0x0F, 2, mode) + def _set_headset_detect(self, enable, detect_debounce=0, button_debounce=0): """Configure headset detection settings.""" value = (1 if enable else 0) << 7 value |= (detect_debounce & 0x07) << 2 - value |= (button_debounce & 0x03) - - self._write_register(REG_HEADSET_DETECT, value) - return True - - def _set_dac_data_path(self, left_dac_on, right_dac_on, - left_path=DAC_PATH_NORMAL, - right_path=DAC_PATH_NORMAL, - volume_step=VOLUME_STEP_1SAMPLE): + value |= button_debounce & 0x03 + self._write_register(_HEADSET_DETECT, value) + + def _set_dac_data_path( + self, + left_dac_on, + right_dac_on, + left_path=DAC_PATH_NORMAL, + right_path=DAC_PATH_NORMAL, + volume_step=VOLUME_STEP_1SAMPLE, + ): """Configure the DAC data path settings.""" value = 0 - if left_dac_on: value |= (1 << 7) - if right_dac_on: value |= (1 << 6) + if left_dac_on: + value |= 1 << 7 + if right_dac_on: + value |= 1 << 6 value |= (left_path & 0x03) << 4 value |= (right_path & 0x03) << 2 - value |= (volume_step & 0x03) - - self._write_register(REG_DAC_DATAPATH, value) - return True - + value |= volume_step & 0x03 + self._write_register(_DAC_DATAPATH, value) + def _set_dac_volume_control(self, left_mute, right_mute, control=VOL_INDEPENDENT): """Configure the DAC volume control settings.""" value = 0 - if left_mute: value |= (1 << 3) - if right_mute: value |= (1 << 2) - value |= (control & 0x03) - - self._write_register(REG_DAC_VOL_CTRL, value) - return True - + if left_mute: + value |= 1 << 3 + if right_mute: + value |= 1 << 2 + value |= control & 0x03 + self._write_register(_DAC_VOL_CTRL, value) + def _set_channel_volume(self, right_channel, db): """Set DAC channel volume in dB.""" - # Constrain input to valid range if db > 24.0: db = 24.0 if db < -63.5: db = -63.5 - reg_val = int(db * 2) - - # Check for reserved values if reg_val == 0x80 or reg_val > 0x30: return False - + if right_channel: - self._write_register(REG_DAC_RVOL, reg_val & 0xFF) + self._write_register(_DAC_RVOL, reg_val & 0xFF) else: - self._write_register(REG_DAC_LVOL, reg_val & 0xFF) - - return True - + self._write_register(_DAC_LVOL, reg_val & 0xFF) + def _get_dac_flags(self): """Get the DAC and output driver status flags. - + :return: Dictionary with status flags for various components """ - # Read first flag register - flag_reg = self._read_register(REG_DAC_FLAG) - left_dac_powered = bool(flag_reg & (1 << 7)) # bit 7 - hpl_powered = bool(flag_reg & (1 << 5)) # bit 5 - left_classd_powered = bool(flag_reg & (1 << 4)) # bit 4 - right_dac_powered = bool(flag_reg & (1 << 3)) # bit 3 - hpr_powered = bool(flag_reg & (1 << 1)) # bit 1 - right_classd_powered = bool(flag_reg & (1 << 0)) # bit 0 - - # Read second flag register - flag2_reg = self._read_register(REG_DAC_FLAG2) - left_pga_gain_ok = bool(flag2_reg & (1 << 4)) # bit 4 - right_pga_gain_ok = bool(flag2_reg & (1 << 0)) # bit 0 - + flag_reg = self._read_register(_DAC_FLAG) + left_dac_powered = bool(flag_reg & (1 << 7)) + hpl_powered = bool(flag_reg & (1 << 5)) + left_classd_powered = bool(flag_reg & (1 << 4)) + right_dac_powered = bool(flag_reg & (1 << 3)) + hpr_powered = bool(flag_reg & (1 << 1)) + right_classd_powered = bool(flag_reg & (1 << 0)) + flag2_reg = self._read_register(_DAC_FLAG2) + left_pga_gain_ok = bool(flag2_reg & (1 << 4)) + right_pga_gain_ok = bool(flag2_reg & (1 << 0)) + return { "left_dac_powered": left_dac_powered, "hpl_powered": hpl_powered, @@ -396,162 +358,155 @@ class Page0Registers(PagedRegisterBase): "hpr_powered": hpr_powered, "right_classd_powered": right_classd_powered, "left_pga_gain_ok": left_pga_gain_ok, - "right_pga_gain_ok": right_pga_gain_ok + "right_pga_gain_ok": right_pga_gain_ok, } def get_gpio1_input(self): """Get the current GPIO1 input value. - + :return: Current GPIO1 input state (True/False) """ - return bool(self._get_bits(REG_GPIO1_CTRL, 0x01, 1)) + return bool(self._get_bits(_GPIO1_CTRL, 0x01, 1)) def _get_din_input(self): """Get the current DIN input value. - + :return: Current DIN input state (True/False) """ - return bool(self._get_bits(REG_DIN_CTRL, 0x01, 0)) + return bool(self._get_bits(_DIN_CTRL, 0x01, 0)) def _get_codec_interface(self): """Get the current codec interface settings. - + :return: Dictionary with format, data_len, bclk_out, and wclk_out values """ - reg_value = self._read_register(REG_CODEC_IF_CTRL1) + reg_value = self._read_register(_CODEC_IF_CTRL1) format_val = (reg_value >> 6) & 0x03 # bits 7:6 - data_len = (reg_value >> 4) & 0x03 # bits 5:4 + data_len = (reg_value >> 4) & 0x03 # bits 5:4 bclk_out = bool(reg_value & (1 << 3)) # bit 3 wclk_out = bool(reg_value & (1 << 2)) # bit 2 - + return { "format": format_val, "data_len": data_len, "bclk_out": bclk_out, - "wclk_out": wclk_out + "wclk_out": wclk_out, } def _get_dac_data_path(self): """Get the current DAC data path configuration. - + :return: Dictionary with DAC data path settings """ - reg_value = self._read_register(REG_DAC_DATAPATH) + reg_value = self._read_register(_DAC_DATAPATH) left_dac_on = bool(reg_value & (1 << 7)) # bit 7 right_dac_on = bool(reg_value & (1 << 6)) # bit 6 left_path = (reg_value >> 4) & 0x03 # bits 5:4 right_path = (reg_value >> 2) & 0x03 # bits 3:2 volume_step = reg_value & 0x03 # bits 1:0 - + return { "left_dac_on": left_dac_on, "right_dac_on": right_dac_on, "left_path": left_path, "right_path": right_path, - "volume_step": volume_step + "volume_step": volume_step, } def _get_dac_volume_control(self): """Get the current DAC volume control configuration. - + :return: Dictionary with volume control settings """ - reg_value = self._read_register(REG_DAC_VOL_CTRL) + reg_value = self._read_register(_DAC_VOL_CTRL) left_mute = bool(reg_value & (1 << 3)) # bit 3 right_mute = bool(reg_value & (1 << 2)) # bit 2 control = reg_value & 0x03 # bits 1:0 - - return { - "left_mute": left_mute, - "right_mute": right_mute, - "control": control - } + + return {"left_mute": left_mute, "right_mute": right_mute, "control": control} def _get_channel_volume(self, right_channel): """Get DAC channel volume in dB. - + :param right_channel: True for right channel, False for left channel :return: Current volume in dB """ - reg = REG_DAC_RVOL if right_channel else REG_DAC_LVOL + reg = _DAC_RVOL if right_channel else _DAC_LVOL reg_val = self._read_register(reg) - - # Convert to signed value if needed if reg_val & 0x80: - steps = reg_val - 256 # Two's complement conversion + steps = reg_val - 256 else: steps = reg_val - - # Convert to dB (each step is 0.5 dB) return steps * 0.5 - + def _get_headset_status(self): """Get current headset detection status. - + :return: Integer value representing headset status (0=none, 1=without mic, 3=with mic) """ - status_bits = self._get_bits(REG_HEADSET_DETECT, 0x03, 5) + status_bits = self._get_bits(_HEADSET_DETECT, 0x03, 5) return status_bits - + def _config_vol_adc(self, pin_control=False, use_mclk=False, hysteresis=0, rate=0): """Configure the Volume/MicDet pin ADC. - + :param pin_control: Enable pin control of DAC volume :param use_mclk: Use MCLK instead of internal RC oscillator :param hysteresis: ADC hysteresis setting (0-2) :param rate: ADC sampling rate (0-7) - :return: True if successful """ value = (1 if pin_control else 0) << 7 value |= (1 if use_mclk else 0) << 6 value |= (hysteresis & 0x03) << 4 - value |= (rate & 0x07) - - self._write_register(REG_VOL_ADC_CTRL, value) - return True + value |= rate & 0x07 + self._write_register(_VOL_ADC_CTRL, value) def _read_vol_adc_db(self): """Read the current volume from the Volume ADC in dB. - + :return: Current volume in dB (+18 to -63 dB) """ - raw_val = self._read_register(REG_VOL_ADC_READ) & 0x7F - - # Check for reserved value + raw_val = self._read_register(_VOL_ADC_READ) & 0x7F if raw_val == 0x7F: return 0.0 - - # Convert register value to dB - # 0x00 = +18dB, 0x24 = 0dB, 0x7E = -63dB if raw_val <= 0x24: - # Positive or zero dB range return 18.0 - (raw_val * 0.5) else: - # Negative dB range return -((raw_val - 0x24) * 0.5) - def _set_int2_source(self, headset_detect=False, button_press=False, dac_drc=False, - agc_noise=False, over_current=False, multiple_pulse=False): + def _set_int2_source( + self, + headset_detect=False, + button_press=False, + dac_drc=False, + agc_noise=False, + over_current=False, + multiple_pulse=False, + ): """Configure the INT2 interrupt sources. - + :param headset_detect: Enable headset detection interrupt :param button_press: Enable button press detection interrupt :param dac_drc: Enable DAC DRC signal power interrupt :param agc_noise: Enable DAC data overflow interrupt :param over_current: Enable short circuit interrupt :param multiple_pulse: If true, INT2 generates multiple pulses until flag read - :return: True if successful """ value = 0 - if headset_detect: value |= (1 << 7) - if button_press: value |= (1 << 6) - if dac_drc: value |= (1 << 5) - if over_current: value |= (1 << 3) - if agc_noise: value |= (1 << 2) - if multiple_pulse: value |= (1 << 0) - - self._write_register(REG_INT2_CTRL, value) - return True + if headset_detect: + value |= 1 << 7 + if button_press: + value |= 1 << 6 + if dac_drc: + value |= 1 << 5 + if over_current: + value |= 1 << 3 + if agc_noise: + value |= 1 << 2 + if multiple_pulse: + value |= 1 << 0 + + self._write_register(_INT2_CTRL, value) def _set_codec_interface(self, format, data_len, bclk_out=False, wclk_out=False): """Set the codec interface parameters.""" @@ -559,308 +514,246 @@ class Page0Registers(PagedRegisterBase): value |= (data_len & 0x03) << 4 value |= (1 if bclk_out else 0) << 3 value |= (1 if wclk_out else 0) << 2 - - self._write_register(REG_CODEC_IF_CTRL1, value) - return True + + self._write_register(_CODEC_IF_CTRL1, value) def _configure_clocks_for_sample_rate(self, mclk_freq, sample_rate, bit_depth): """Configure clock settings for the specified sample rate. - - :param mclk_freq: The master clock frequency in Hz + + :param mclk_freq: The main clock frequency in Hz :param sample_rate: The desired sample rate in Hz :param bit_depth: The bit depth (16, 20, 24, or 32) - :return: True if successful, False otherwise """ - # Map bit depth to register value if bit_depth == 16: data_len = DATA_LEN_16 elif bit_depth == 20: data_len = DATA_LEN_20 elif bit_depth == 24: data_len = DATA_LEN_24 - else: # Default to 32-bit for any other value + else: data_len = DATA_LEN_32 - - # First check if we can directly divide MCLK to get the sample rate if mclk_freq % (128 * sample_rate) == 0: - # Simple case: configure without PLL div_ratio = mclk_freq // (128 * sample_rate) - - # Set clock inputs to use MCLK directly - self._set_bits(REG_CLOCK_MUX1, 0x03, 0, 0b00) # CODEC_CLKIN = MCLK - - # Turn off PLL - self._set_bits(REG_PLL_PROG_PR, 0x01, 7, 0) # Power down PLL - - # Configure dividers + self._set_bits(_CLOCK_MUX1, 0x03, 0, 0b00) + self._set_bits(_PLL_PROG_PR, 0x01, 7, 0) if div_ratio <= 128: - # Configure NDAC - self._write_register(REG_NDAC, 0x80 | (div_ratio & 0x7F)) # Enable with value - - # Configure MDAC - self._write_register(REG_MDAC, 0x81) # Enable with value 1 - - # Configure DOSR (128 is typical) - self._write_register(REG_DOSR_MSB, 0) - self._write_register(REG_DOSR_LSB, 128) - - # Set codec interface format + self._write_register(_NDAC, 0x80 | (div_ratio & 0x7F)) + self._write_register(_MDAC, 0x81) + self._write_register(_DOSR_MSB, 0) + self._write_register(_DOSR_LSB, 128) self._set_codec_interface(FORMAT_I2S, data_len) - return True - - # If direct division doesn't work, use PLL - # Lookup tables for common configurations + if mclk_freq == 12000000: if sample_rate == 22050: - # Configuration for 22.05kHz from 12MHz p, r, j, d = 1, 1, 7, 6144 - ndac = 8 # Double the divisor compared to 44.1kHz + ndac = 8 mdac = 1 dosr = 128 elif sample_rate == 44100: - # Common configuration for 44.1kHz from 12MHz p, r, j, d = 1, 1, 7, 6144 ndac = 4 mdac = 1 dosr = 128 elif sample_rate == 48000: - # Common configuration for 48kHz from 12MHz p, r, j, d = 1, 1, 8, 0 ndac = 4 mdac = 1 dosr = 128 elif sample_rate == 96000: - # Configuration for 96kHz from 12MHz p, r, j, d = 1, 1, 8, 0 ndac = 2 mdac = 1 dosr = 128 else: - # Not a supported sample rate for 12MHz MCLK return False elif mclk_freq == 24000000: if sample_rate == 44100: - # Configuration for 44.1kHz from 24MHz p, r, j, d = 1, 2, 7, 6144 ndac = 4 mdac = 1 dosr = 128 elif sample_rate == 48000: - # Configuration for 48kHz from 24MHz p, r, j, d = 1, 2, 8, 0 ndac = 4 mdac = 1 dosr = 128 elif sample_rate == 96000: - # Configuration for 96kHz from 24MHz p, r, j, d = 1, 2, 8, 0 ndac = 2 mdac = 1 dosr = 128 else: - # Not a supported sample rate for 24MHz MCLK - return False + raise ValueError("Need a valid sample rate: 44100, 48000 or 96000") else: - # Unsupported MCLK frequency - return False - - # Configure PLL - # Set clock sources - self._set_bits(REG_CLOCK_MUX1, 0x03, 2, 0b00) # PLL_CLKIN = MCLK - self._set_bits(REG_CLOCK_MUX1, 0x03, 0, 0b11) # CODEC_CLKIN = PLL - - # Set P and R values + raise ValueError("Need a valid sample rate: 22050, 44100, 48000 or 96000") + + self._set_bits(_CLOCK_MUX1, 0x03, 2, 0b00) + self._set_bits(_CLOCK_MUX1, 0x03, 0, 0b11) pr_value = ((p & 0x07) << 4) | (r & 0x0F) - self._write_register(REG_PLL_PROG_PR, pr_value & 0x7F) # Don't enable PLL yet - - # Set J value - self._write_register(REG_PLL_PROG_J, j & 0x3F) - - # Set D value (14 bits) - self._write_register(REG_PLL_PROG_D_MSB, (d >> 8) & 0xFF) - self._write_register(REG_PLL_PROG_D_LSB, d & 0xFF) - - # Configure dividers - # NDAC - self._write_register(REG_NDAC, 0x80 | (ndac & 0x7F)) # Enable with value - - # MDAC - self._write_register(REG_MDAC, 0x80 | (mdac & 0x7F)) # Enable with value - - # DOSR - self._write_register(REG_DOSR_MSB, (dosr >> 8) & 0xFF) - self._write_register(REG_DOSR_LSB, dosr & 0xFF) - - # Set codec interface format + self._write_register(_PLL_PROG_PR, pr_value & 0x7F) + self._write_register(_PLL_PROG_J, j & 0x3F) + self._write_register(_PLL_PROG_D_MSB, (d >> 8) & 0xFF) + self._write_register(_PLL_PROG_D_LSB, d & 0xFF) + self._write_register(_NDAC, 0x80 | (ndac & 0x7F)) + self._write_register(_MDAC, 0x80 | (mdac & 0x7F)) + self._write_register(_DOSR_MSB, (dosr >> 8) & 0xFF) + self._write_register(_DOSR_LSB, dosr & 0xFF) self._set_codec_interface(FORMAT_I2S, data_len) - - # Power up PLL - self._set_bits(REG_PLL_PROG_PR, 0x01, 7, 1) # Power up PLL - - # Wait for PLL to lock - time.sleep(0.01) # 10ms should be enough for PLL to stabilize - - return True + self._set_bits(_PLL_PROG_PR, 0x01, 7, 1) + time.sleep(0.01) class Page1Registers(PagedRegisterBase): """Page 1 registers containing analog output settings, HP/SPK controls, etc.""" - + def __init__(self, i2c_device): """Initialize Page 1 registers. - + :param i2c_device: The I2C device """ super().__init__(i2c_device, 1) - + def _get_speaker_enabled(self): """Check if speaker is enabled.""" - return bool(self._get_bits(REG_SPK_AMP, 0x01, 7)) - + return bool(self._get_bits(_SPK_AMP, 0x01, 7)) + def _set_speaker_enabled(self, enable): """Enable or disable the Class-D speaker amplifier.""" - return self._set_bits(REG_SPK_AMP, 0x01, 7, 1 if enable else 0) - - def _configure_headphone_driver(self, left_powered, right_powered, - common=HP_COMMON_1_35V, power_down_on_scd=False): + return self._set_bits(_SPK_AMP, 0x01, 7, 1 if enable else 0) + + def _configure_headphone_driver( + self, left_powered, right_powered, common=HP_COMMON_1_35V, power_down_on_scd=False + ): """Configure headphone driver settings.""" value = 0x04 # bit 2 must be 1 - if left_powered: value |= (1 << 7) - if right_powered: value |= (1 << 6) + if left_powered: + value |= 1 << 7 + if right_powered: + value |= 1 << 6 value |= (common & 0x03) << 3 - if power_down_on_scd: value |= (1 << 1) - - self._write_register(REG_HP_DRIVERS, value) - return True - - def _configure_analog_inputs(self, left_dac=DAC_ROUTE_NONE, right_dac=DAC_ROUTE_NONE, - left_ain1=False, left_ain2=False, right_ain2=False, - hpl_routed_to_hpr=False): + if power_down_on_scd: + value |= 1 << 1 + + self._write_register(_HP_DRIVERS, value) + + def _configure_analog_inputs( + self, + left_dac=DAC_ROUTE_NONE, + right_dac=DAC_ROUTE_NONE, + left_ain1=False, + left_ain2=False, + right_ain2=False, + hpl_routed_to_hpr=False, + ): """Configure DAC and analog input routing.""" value = 0 value |= (left_dac & 0x03) << 6 - if left_ain1: value |= (1 << 5) - if left_ain2: value |= (1 << 4) + if left_ain1: + value |= 1 << 5 + if left_ain2: + value |= 1 << 4 value |= (right_dac & 0x03) << 2 - if right_ain2: value |= (1 << 1) - if hpl_routed_to_hpr: value |= 1 - - self._write_register(REG_OUT_ROUTING, value) - return True - + if right_ain2: + value |= 1 << 1 + if hpl_routed_to_hpr: + value |= 1 + + self._write_register(_OUT_ROUTING, value) + def _set_hpl_volume(self, route_enabled, gain=0x7F): """Set HPL analog volume control.""" - if gain > 0x7F: + if gain > 0x7F: gain = 0x7F - value = ((1 if route_enabled else 0) << 7) | (gain & 0x7F) - self._write_register(REG_HPL_VOL, value) - return True - + self._write_register(_HPL_VOL, value) + def _set_hpr_volume(self, route_enabled, gain=0x7F): """Set HPR analog volume control.""" - if gain > 0x7F: + if gain > 0x7F: gain = 0x7F - value = ((1 if route_enabled else 0) << 7) | (gain & 0x7F) - self._write_register(REG_HPR_VOL, value) - return True - + self._write_register(_HPR_VOL, value) + def _set_spk_volume(self, route_enabled, gain=0x7F): """Set Speaker analog volume control.""" - if gain > 0x7F: + if gain > 0x7F: gain = 0x7F - value = ((1 if route_enabled else 0) << 7) | (gain & 0x7F) - self._write_register(REG_SPK_VOL, value) - return True - + self._write_register(_SPK_VOL, value) + def _configure_hpl_pga(self, gain_db=0, unmute=True): """Configure HPL driver PGA settings.""" if gain_db > 9: - return False - + raise ValueError("Gain cannot be greater than 9") value = (gain_db & 0x0F) << 3 - if unmute: value |= (1 << 2) - - self._write_register(REG_HPL_DRIVER, value) - return True - + if unmute: + value |= 1 << 2 + self._write_register(_HPL_DRIVER, value) + def _configure_hpr_pga(self, gain_db=0, unmute=True): """Configure HPR driver PGA settings.""" if gain_db > 9: - return False - + raise ValueError("Gain cannot be greater than 9") value = (gain_db & 0x0F) << 3 - if unmute: value |= (1 << 2) - - self._write_register(REG_HPR_DRIVER, value) - return True - + if unmute: + value |= 1 << 2 + self._write_register(_HPR_DRIVER, value) + def _configure_spk_pga(self, gain=SPK_GAIN_6DB, unmute=True): """Configure Speaker driver settings.""" value = (gain & 0x03) << 3 - if unmute: value |= (1 << 2) - - self._write_register(REG_SPK_DRIVER, value) - return True - - def _is_headphone_shorted(self): - """Check if headphone short circuit is detected. - - :return: True if short circuit detected, False if not - """ - return False + if unmute: + value |= 1 << 2 + self._write_register(_SPK_DRIVER, value) def _is_speaker_shorted(self): """Check if speaker short circuit is detected. - + :return: True if short circuit detected, False if not """ - return bool(self._get_bits(REG_SPK_AMP, 0x01, 0)) + return bool(self._get_bits(_SPK_AMP, 0x01, 0)) def _is_hpl_gain_applied(self): """Check if all programmed gains have been applied to HPL. - + :return: True if gains applied, False if still ramping """ - return bool(self._get_bits(REG_HPL_DRIVER, 0x01, 0)) + return bool(self._get_bits(_HPL_DRIVER, 0x01, 0)) def _is_hpr_gain_applied(self): """Check if all programmed gains have been applied to HPR. - + :return: True if gains applied, False if still ramping """ - return bool(self._get_bits(REG_HPR_DRIVER, 0x01, 0)) + return bool(self._get_bits(_HPR_DRIVER, 0x01, 0)) def _is_spk_gain_applied(self): """Check if all programmed gains have been applied to Speaker. - + :return: True if gains applied, False if still ramping """ - return bool(self._get_bits(REG_SPK_DRIVER, 0x01, 0)) - + return bool(self._get_bits(_SPK_DRIVER, 0x01, 0)) + def _reset_speaker_on_scd(self, reset): """Configure speaker reset behavior on short circuit detection. - + :param reset: True to reset speaker on short circuit, False to remain unchanged :return: True if successful """ - # Register is inverse of parameter (0 = reset, 1 = no reset) - return self._set_bits(REG_HP_SPK_ERR_CTL, 0x01, 1, 0 if reset else 1) + return self._set_bits(_HP_SPK_ERR_CTL, 0x01, 1, 0 if reset else 1) def _reset_headphone_on_scd(self, reset): """Configure headphone reset behavior on short circuit detection. - + :param reset: True to reset headphone on short circuit, False to remain unchanged :return: True if successful """ # Register is inverse of parameter (0 = reset, 1 = no reset) - return self._set_bits(REG_HP_SPK_ERR_CTL, 0x01, 0, 0 if reset else 1) + return self._set_bits(_HP_SPK_ERR_CTL, 0x01, 0, 0 if reset else 1) def _configure_headphone_pop(self, wait_for_powerdown=True, powerup_time=0x07, ramp_time=0x03): """Configure headphone pop removal settings. - + :param wait_for_powerdown: Wait for amp powerdown before DAC powerdown :param powerup_time: Driver power-on time (0-11) :param ramp_time: Driver ramp-up step time (0-3) @@ -869,863 +762,1114 @@ class Page1Registers(PagedRegisterBase): value = (1 if wait_for_powerdown else 0) << 7 value |= (powerup_time & 0x0F) << 3 value |= (ramp_time & 0x03) << 1 - - self._write_register(REG_HP_POP, value) - return True + self._write_register(_HP_POP, value) def _set_speaker_wait_time(self, wait_time=0): """Set speaker power-up wait time. - + :param wait_time: Speaker power-up wait duration (0-7) :return: True if successful """ - return self._set_bits(REG_PGA_RAMP, 0x07, 4, wait_time) + return self._set_bits(_PGA_RAMP, 0x07, 4, wait_time) def _headphone_lineout(self, left, right): """Configure headphone outputs as line-out. - + :param left: Configure left channel as line-out :param right: Configure right channel as line-out :return: True if successful """ value = 0 - if left: value |= (1 << 2) - if right: value |= (1 << 1) - - self._write_register(REG_HP_DRIVER_CTRL, value) - return True + if left: + value |= 1 << 2 + if right: + value |= 1 << 1 + self._write_register(_HP_DRIVER_CTRL, value) def _config_mic_bias(self, power_down=False, always_on=False, voltage=0): """Configure MICBIAS settings.""" value = (1 if power_down else 0) << 7 value |= (1 if always_on else 0) << 3 - value |= (voltage & 0x03) - - self._write_register(REG_MICBIAS, value) # Using constant instead of 0x2E - return True + value |= voltage & 0x03 + self._write_register(_MICBIAS, value) # Using constant instead of 0x2E def _set_input_common_mode(self, ain1_cm, ain2_cm): """Set analog input common mode connections.""" value = 0 - if ain1_cm: value |= (1 << 7) - if ain2_cm: value |= (1 << 6) - - self._write_register(REG_INPUT_CM, value) # Using constant instead of 0x32 - return True + if ain1_cm: + value |= 1 << 7 + if ain2_cm: + value |= 1 << 6 + self._write_register(_INPUT_CM, value) # Using constant instead of 0x32 + class Page3Registers(PagedRegisterBase): """Page 3 registers containing timer settings.""" - + def __init__(self, i2c_device): """Initialize Page 3 registers. - + :param i2c_device: The I2C device """ super().__init__(i2c_device, 3) - + def _config_delay_divider(self, use_mclk=True, divider=1): """Configure programmable delay timer clock source and divider.""" value = (1 if use_mclk else 0) << 7 - value |= (divider & 0x7F) - - self._write_register(REG_TIMER_MCLK_DIV, value) # Using constant instead of 0x10 - return True + value |= divider & 0x7F + self._write_register(_TIMER_MCLK_DIV, value) class TLV320DAC3100: """Driver for the TI TLV320DAC3100 Stereo DAC with Headphone Amplifier.""" - def __init__(self, i2c, address=I2C_ADDR_DEFAULT): + def __init__(self, i2c: I2C, address: int = 0x18) -> None: """Initialize the TLV320DAC3100. - + :param i2c: The I2C bus the device is connected to :param address: The I2C device address (default is 0x18) """ - self._device = I2CDevice(i2c, address) - + self._device: I2CDevice = I2CDevice(i2c, address) + # Initialize register page classes - self._page0 = Page0Registers(self._device) - self._page1 = Page1Registers(self._device) - self._page3 = Page3Registers(self._device) + self._page0: "Page0Registers" = Page0Registers(self._device) + self._page1: "Page1Registers" = Page1Registers(self._device) + self._page3: "Page3Registers" = Page3Registers(self._device) # Initialize configuration tracking variables - self._sample_rate = 44100 # Default - self._bit_depth = 16 # Default - self._mclk_freq = 12000000 # Default - + self._sample_rate: int = 44100 # Default + self._bit_depth: int = 16 # Default + self._mclk_freq: int = 12000000 # Default + # Reset the device if not self.reset(): raise RuntimeError("Failed to reset TLV320DAC3100") - + # Basic properties and methods - - def reset(self): - """Reset the device.""" + + def reset(self) -> bool: + """Reset the device. + + :return: True if reset successful, False otherwise + """ return self._page0._reset() - - @property - def overtemperature(self): - """Check if the chip is overheating.""" - return self._page0._is_overtemperature() - - # Method-based API for complex operations - - def set_headset_detect(self, enable, detect_debounce=0, button_debounce=0): - """Configure headset detection settings.""" - return self._page0._set_headset_detect(enable, detect_debounce, button_debounce) - - def int1_source(self, headset_detect, button_press, dac_drc, - agc_noise, over_current, multiple_pulse): - """Configure the INT1 interrupt sources.""" - return self._page0._set_int1_source(headset_detect, button_press, dac_drc, - agc_noise, over_current, multiple_pulse) @property - def left_dac(self): - """Get the left DAC enabled status.""" - return self._page0._get_dac_data_path()['left_dac_on'] + def overtemperature(self) -> bool: + """Check if the chip is overheating. + + :return: True if overtemperature condition exists, False otherwise + """ + return self._page0._is_overtemperature() + + def set_headset_detect( + self, enable: bool, detect_debounce: int = 0, button_debounce: int = 0 + ) -> bool: + """Configure headset detection settings. + + :param enable: Boolean to enable/disable headset detection + :param detect_debounce: One of the DEBOUNCE_* constants for headset detect + :param button_debounce: One of the BTN_DEBOUNCE_* constants for button press + :raises ValueError: If debounce values are not valid constants + :return: True if successful, False otherwise + """ + valid_detect_debounce: List[int] = [ + DEBOUNCE_16MS, + DEBOUNCE_32MS, + DEBOUNCE_64MS, + DEBOUNCE_128MS, + DEBOUNCE_256MS, + DEBOUNCE_512MS, + ] + valid_button_debounce: List[int] = [ + BTN_DEBOUNCE_0MS, + BTN_DEBOUNCE_8MS, + BTN_DEBOUNCE_16MS, + BTN_DEBOUNCE_32MS, + ] + + if detect_debounce not in valid_detect_debounce: + raise ValueError( + f"Invalid detect_debounce value: {detect_debounce}." + + "Must be one of the DEBOUNCE_* constants." + ) + + if button_debounce not in valid_button_debounce: + raise ValueError( + f"Invalid button_debounce value: {button_debounce}." + + "Must be one of the BTN_DEBOUNCE_* constants." + ) + + return self._page0._set_headset_detect(enable, detect_debounce, button_debounce) + + def int1_source( + self, + headset_detect: bool, + button_press: bool, + dac_drc: bool, + agc_noise: bool, + over_current: bool, + multiple_pulse: bool, + ) -> bool: + """Configure the INT1 interrupt sources. + + :param headset_detect: Enable headset detection interrupt + :param button_press: Enable button press detection interrupt + :param dac_drc: Enable DAC DRC signal power interrupt + :param agc_noise: Enable DAC data overflow interrupt + :param over_current: Enable short circuit interrupt + :param multiple_pulse: If true, INT1 generates multiple pulses until flag read + :return: True if successful, False otherwise + """ + return self._page0._set_int1_source( + headset_detect, button_press, dac_drc, agc_noise, over_current, multiple_pulse + ) + + @property + def left_dac(self) -> bool: + """Get the left DAC enabled status. + + :return: True if left DAC is enabled, False otherwise + """ + return self._page0._get_dac_data_path()["left_dac_on"] @left_dac.setter - def left_dac(self, enabled): - """Set the left DAC enabled status.""" - current = self._page0._get_dac_data_path() + def left_dac(self, enabled: bool) -> None: + """Set the left DAC enabled status. + + :param enabled: True to enable left DAC, False to disable + """ + current: DACDataPath = self._page0._get_dac_data_path() self._page0._set_dac_data_path( - enabled, - current['right_dac_on'], - current['left_path'], - current['right_path'], - current['volume_step'] + enabled, + current["right_dac_on"], + current["left_path"], + current["right_path"], + current["volume_step"], ) @property - def right_dac(self): - """Get the right DAC enabled status.""" - return self._page0._get_dac_data_path()['right_dac_on'] + def right_dac(self) -> bool: + """Get the right DAC enabled status. + + :return: True if right DAC is enabled, False otherwise + """ + return self._page0._get_dac_data_path()["right_dac_on"] @right_dac.setter - def right_dac(self, enabled): - """Set the right DAC enabled status.""" - current = self._page0._get_dac_data_path() + def right_dac(self, enabled: bool) -> None: + """Set the right DAC enabled status. + + :param enabled: True to enable right DAC, False to disable + """ + current: DACDataPath = self._page0._get_dac_data_path() self._page0._set_dac_data_path( - current['left_dac_on'], + current["left_dac_on"], enabled, - current['left_path'], - current['right_path'], - current['volume_step'] + current["left_path"], + current["right_path"], + current["volume_step"], ) @property - def left_dac_path(self): - """Get the left DAC path setting.""" - return self._page0._get_dac_data_path()['left_path'] + def left_dac_path(self) -> int: + """Get the left DAC path setting. + + :return: One of the DAC_PATH_* constants + """ + return self._page0._get_dac_data_path()["left_path"] @left_dac_path.setter - def left_dac_path(self, path): - """Set the left DAC path.""" - current = self._page0._get_dac_data_path() + def left_dac_path(self, path: int) -> None: + """Set the left DAC path. + + :param path: One of the DAC_PATH_* constants + :raises ValueError: If path is not a valid DAC_PATH_* constant + """ + valid_paths: List[int] = [DAC_PATH_OFF, DAC_PATH_NORMAL, DAC_PATH_SWAPPED, DAC_PATH_MIXED] + + if path not in valid_paths: + raise ValueError( + f"Invalid DAC path value: {path}. Must be one of the DAC_PATH_* constants." + ) + + current: DACDataPath = self._page0._get_dac_data_path() self._page0._set_dac_data_path( - current['left_dac_on'], - current['right_dac_on'], + current["left_dac_on"], + current["right_dac_on"], path, - current['right_path'], - current['volume_step'] + current["right_path"], + current["volume_step"], ) @property - def right_dac_path(self): - """Get the right DAC path setting.""" - return self._page0._get_dac_data_path()['right_path'] + def right_dac_path(self) -> int: + """Get the right DAC path setting. + + :return: One of the DAC_PATH_* constants + """ + return self._page0._get_dac_data_path()["right_path"] @right_dac_path.setter - def right_dac_path(self, path): - """Set the right DAC path.""" - current = self._page0._get_dac_data_path() + def right_dac_path(self, path: int) -> None: + """Set the right DAC path. + + :param path: One of the DAC_PATH_* constants + :raises ValueError: If path is not a valid DAC_PATH_* constant + """ + valid_paths: List[int] = [DAC_PATH_OFF, DAC_PATH_NORMAL, DAC_PATH_SWAPPED, DAC_PATH_MIXED] + + if path not in valid_paths: + raise ValueError( + f"Invalid DAC path value: {path}. Must be one of the DAC_PATH_* constants." + ) + + current: DACDataPath = self._page0._get_dac_data_path() self._page0._set_dac_data_path( - current['left_dac_on'], - current['right_dac_on'], - current['left_path'], + current["left_dac_on"], + current["right_dac_on"], + current["left_path"], path, - current['volume_step'] + current["volume_step"], ) @property - def dac_volume_step(self): - """DAC volume step setting.""" - return self._page0._get_dac_data_path()['volume_step'] + def dac_volume_step(self) -> int: + """Get the DAC volume step setting. + + :return: One of the VOLUME_STEP_* constants + """ + return self._page0._get_dac_data_path()["volume_step"] @dac_volume_step.setter - def dac_volume_step(self, step): - current = self._page0._get_dac_data_path() + def dac_volume_step(self, step: int) -> None: + """Set the DAC volume step setting. + + :param step: One of the VOLUME_STEP_* constants + :raises ValueError: If step is not a valid VOLUME_STEP_* constant + """ + valid_steps: List[int] = [VOLUME_STEP_1SAMPLE, VOLUME_STEP_2SAMPLE, VOLUME_STEP_DISABLED] + + if step not in valid_steps: + raise ValueError( + f"Invalid volume step value: {step}. Must be one of the VOLUME_STEP_* constants." + ) + + current: DACDataPath = self._page0._get_dac_data_path() self._page0._set_dac_data_path( - current['left_dac_on'], - current['right_dac_on'], - current['left_path'], - current['right_path'], - step + current["left_dac_on"], + current["right_dac_on"], + current["left_path"], + current["right_path"], + step, ) - - def configure_analog_inputs(self, left_dac=DAC_ROUTE_NONE, right_dac=DAC_ROUTE_NONE, - left_ain1=False, left_ain2=False, right_ain2=False, - hpl_routed_to_hpr=False): - """Configure DAC and analog input routing.""" - return self._page1._configure_analog_inputs(left_dac, right_dac, - left_ain1, left_ain2, - right_ain2, hpl_routed_to_hpr) - + + def configure_analog_inputs( + self, + left_dac: int = 0, + right_dac: int = 0, + left_ain1: bool = False, + left_ain2: bool = False, + right_ain2: bool = False, + hpl_routed_to_hpr: bool = False, + ) -> bool: + """Configure DAC and analog input routing. + + :param left_dac: One of the DAC_ROUTE_* constants for left DAC routing + :param right_dac: One of the DAC_ROUTE_* constants for right DAC routing + :param left_ain1: Boolean to route left AIN1 to output + :param left_ain2: Boolean to route left AIN2 to output + :param right_ain2: Boolean to route right AIN2 to output + :param hpl_routed_to_hpr: Boolean to route HPL to HPR + :raises ValueError: If DAC route values are not valid constants + :return: True if successful, False otherwise + """ + valid_dac_routes: List[int] = [DAC_ROUTE_NONE, DAC_ROUTE_MIXER, DAC_ROUTE_HP] + + if left_dac not in valid_dac_routes: + raise ValueError( + f"Invalid left_dac value: {left_dac}. Must be one of the DAC_ROUTE_* constants." + ) + + if right_dac not in valid_dac_routes: + raise ValueError( + f"Invalid right_dac value: {right_dac}. Must be one of the DAC_ROUTE_* constants." + ) + + return self._page1._configure_analog_inputs( + left_dac, right_dac, left_ain1, left_ain2, right_ain2, hpl_routed_to_hpr + ) + @property - def left_dac_mute(self): - """Get the left DAC mute status.""" - return self._page0._get_dac_volume_control()['left_mute'] + def left_dac_mute(self) -> bool: + """Get the left DAC mute status. + + :return: True if left DAC is muted, False otherwise + """ + return self._page0._get_dac_volume_control()["left_mute"] @left_dac_mute.setter - def left_dac_mute(self, mute): - """Set the left DAC mute status.""" - current = self._page0._get_dac_volume_control() - self._page0._set_dac_volume_control(mute, current['right_mute'], current['control']) + def left_dac_mute(self, mute: bool) -> None: + """Set the left DAC mute status. + + :param mute: True to mute left DAC, False to unmute + """ + current: DACVolumeControl = self._page0._get_dac_volume_control() + self._page0._set_dac_volume_control(mute, current["right_mute"], current["control"]) @property - def right_dac_mute(self): - """Get the right DAC mute status.""" - return self._page0._get_dac_volume_control()['right_mute'] + def right_dac_mute(self) -> bool: + """Get the right DAC mute status. + + :return: True if right DAC is muted, False otherwise + """ + return self._page0._get_dac_volume_control()["right_mute"] @right_dac_mute.setter - def right_dac_mute(self, mute): - """Set the right DAC mute status.""" - current = self._page0._get_dac_volume_control() - self._page0._set_dac_volume_control(current['left_mute'], mute, current['control']) + def right_dac_mute(self, mute: bool) -> None: + """Set the right DAC mute status. + + :param mute: True to mute right DAC, False to unmute + """ + current: DACVolumeControl = self._page0._get_dac_volume_control() + self._page0._set_dac_volume_control(current["left_mute"], mute, current["control"]) @property - def dac_volume_control_mode(self): - """DAC volume control mode.""" - return self._page0._get_dac_volume_control()['control'] + def dac_volume_control_mode(self) -> int: + """Get the DAC volume control mode. + + :return: One of the VOL_* constants + """ + return self._page0._get_dac_volume_control()["control"] @dac_volume_control_mode.setter - def dac_volume_control_mode(self, mode): - """Set the volume control mode.""" - current = self._page0._get_dac_volume_control() - self._page0._set_dac_volume_control(current['left_mute'], current['right_mute'], mode) - + def dac_volume_control_mode(self, mode: int) -> None: + """Set the volume control mode. + + :param mode: One of the VOL_* constants for volume control mode + :raises ValueError: If mode is not a valid VOL_* constant + """ + valid_modes: List[int] = [VOL_INDEPENDENT, VOL_LEFT_TO_RIGHT, VOL_RIGHT_TO_LEFT] + if mode not in valid_modes: + raise ValueError( + f"Invalid volume control mode: {mode}. Must be one of the VOL_* constants." + ) + current: DACVolumeControl = self._page0._get_dac_volume_control() + self._page0._set_dac_volume_control(current["left_mute"], current["right_mute"], mode) + @property - def left_channel_volume(self): - """Get left DAC channel volume in dB.""" + def left_dac_channel_volume(self) -> float: + """Get left DAC channel volume in dB. + + :return: Volume in dB + """ return self._page0._get_channel_volume(False) - @left_channel_volume.setter - def left_channel_volume(self, db): - """Set left DAC channel volume in dB.""" + @left_dac_channel_volume.setter + def left_dac_channel_volume(self, db: float) -> None: + """Set left DAC channel volume in dB. + + :param db: Volume in dB + """ self._page0._set_channel_volume(False, db) @property - def right_channel_volume(self): - """Get right DAC channel volume in dB.""" + def right_dac_channel_volume(self) -> float: + """Get right DAC channel volume in dB. + + :return: Volume in dB + """ return self._page0._get_channel_volume(True) - @right_channel_volume.setter - def right_channel_volume(self, db): - """Set right DAC channel volume in dB.""" + @right_dac_channel_volume.setter + def right_dac_channel_volume(self, db: float) -> None: + """Set right DAC channel volume in dB. + + :param db: Volume in dB + """ self._page0._set_channel_volume(True, db) - - def manual_headphone_driver(self, left_powered, right_powered, - common=HP_COMMON_1_35V, power_down_on_scd=False): - """Configure headphone driver settings.""" - return self._page1._configure_headphone_driver(left_powered, right_powered, - common, power_down_on_scd) - - def manual_headphone_left_volume(self, route_enabled, gain=0x7F): - """Set HPL analog volume control.""" + + def manual_headphone_driver( + self, + left_powered: bool, + right_powered: bool, + common: int = 0, + power_down_on_scd: bool = False, + ) -> bool: + """Configure headphone driver settings. + + :param left_powered: Boolean to power left headphone driver + :param right_powered: Boolean to power right headphone driver + :param common: One of the HP_COMMON_* constants for common mode voltage + :param power_down_on_scd: Boolean to power down on short circuit detection + :raises ValueError: If common is not a valid HP_COMMON_* constant + :return: True if successful, False otherwise + """ + valid_common_modes: List[int] = [ + HP_COMMON_1_35V, + HP_COMMON_1_50V, + HP_COMMON_1_65V, + HP_COMMON_1_80V, + ] + + if common not in valid_common_modes: + raise ValueError( + f"Invalid common mode value: {common}. Must be one of the HP_COMMON_* constants." + ) + + return self._page1._configure_headphone_driver( + left_powered, right_powered, common, power_down_on_scd + ) + + def manual_headphone_left_volume(self, route_enabled: bool, gain: int = 0x7F) -> bool: + """Set HPL analog volume control. + + :param route_enabled: Enable routing to HPL + :param gain: Analog volume control value (0-127) + :return: True if successful, False otherwise + """ return self._page1._set_hpl_volume(route_enabled, gain) - - def manual_headphone_right_volume(self, route_enabled, gain=0x7F): - """Set HPR analog volume control.""" + + def manual_headphone_right_volume(self, route_enabled: bool, gain: int = 0x7F) -> bool: + """Set HPR analog volume control. + + :param route_enabled: Enable routing to HPR + :param gain: Analog volume control value (0-127) + :return: True if successful, False otherwise + """ return self._page1._set_hpr_volume(route_enabled, gain) @property - def headphone_left_gain(self): - """Get the left headphone gain in dB.""" - reg_value = self._page1._read_register(REG_HPL_DRIVER) + def headphone_left_gain(self) -> int: + """Get the left headphone gain in dB. + + :return: Gain value in dB + """ + reg_value = self._page1._read_register(_HPL_DRIVER) return (reg_value >> 3) & 0x0F @headphone_left_gain.setter - def headphone_left_gain(self, gain_db): - """Set the left headphone gain in dB.""" + def headphone_left_gain(self, gain_db: int) -> None: + """Set the left headphone gain in dB. + + :param gain_db: Gain value in dB + """ unmute = not self.headphone_left_mute self._page1._configure_hpl_pga(gain_db, unmute) @property - def headphone_left_mute(self): - """Get the left headphone mute status.""" - reg_value = self._page1._read_register(REG_HPL_DRIVER) - return not bool(reg_value & (1 << 2)) # Inverse of unmute bit + def headphone_left_mute(self) -> bool: + """Get the left headphone mute status. + + :return: True if left headphone is muted, False otherwise + """ + reg_value = self._page1._read_register(_HPL_DRIVER) + return not bool(reg_value & (1 << 2)) @headphone_left_mute.setter - def headphone_left_mute(self, mute): - """Set the left headphone mute status.""" + def headphone_left_mute(self, mute: bool) -> None: + """Set the left headphone mute status. + + :param mute: True to mute left headphone, False to unmute + """ gain = self.headphone_left_gain - self._page1._configure_hpl_pga(gain, not mute) # Unmute is inverse of mute + self._page1._configure_hpl_pga(gain, not mute) @property - def headphone_right_gain(self): - """Get the right headphone gain in dB.""" - reg_value = self._page1._read_register(REG_HPR_DRIVER) + def headphone_right_gain(self) -> int: + """Get the right headphone gain in dB. + + :return: Gain value in dB + """ + reg_value = self._page1._read_register(_HPR_DRIVER) return (reg_value >> 3) & 0x0F @headphone_right_gain.setter - def headphone_right_gain(self, gain_db): - """Set the right headphone gain in dB.""" + def headphone_right_gain(self, gain_db: int) -> None: + """Set the right headphone gain in dB. + + :param gain_db: Gain value in dB + """ unmute = not self.headphone_right_mute self._page1._configure_hpr_pga(gain_db, unmute) @property - def headphone_right_mute(self): - """Get the right headphone mute status.""" - reg_value = self._page1._read_register(REG_HPR_DRIVER) - return not bool(reg_value & (1 << 2)) # Inverse of unmute bit + def headphone_right_mute(self) -> bool: + """Get the right headphone mute status. + + :return: True if right headphone is muted, False otherwise + """ + reg_value = self._page1._read_register(_HPR_DRIVER) + return not bool(reg_value & (1 << 2)) @headphone_right_mute.setter - def headphone_right_mute(self, mute): - """Set the right headphone mute status.""" + def headphone_right_mute(self, mute: bool) -> None: + """Set the right headphone mute status. + + :param mute: True to mute right headphone, False to unmute + """ gain = self.headphone_right_gain - self._page1._configure_hpr_pga(gain, not mute) # Unmute is inverse of mute - + self._page1._configure_hpr_pga(gain, not mute) + @property - def speaker_gain(self): - """Get the speaker gain setting.""" - reg_value = self._page1._read_register(REG_SPK_DRIVER) + def speaker_gain(self) -> int: + """Get the speaker gain setting in dB. + + :return: The gain value in dB + """ + reg_value = self._page1._read_register(_SPK_DRIVER) return (reg_value >> 3) & 0x03 @speaker_gain.setter - def speaker_gain(self, gain): - """Set the speaker gain.""" + def speaker_gain(self, gain_db: int) -> None: + """Set the speaker gain in dB. + + :param gain_db: Speaker gain in dB (6, 12, 18, or 24) + :raises ValueError: If gain_db is not a valid value + """ + # Convert dB to register value + gain_mapping: List[int] = [SPK_GAIN_6DB, SPK_GAIN_12DB, SPK_GAIN_18DB, SPK_GAIN_24DB] + + if gain_db not in gain_mapping: + raise ValueError( + f"Invalid preset value: {gain_db}. Must be one of the SPK_GAIN_* constants." + ) unmute = not self.speaker_mute - self._page1._configure_spk_pga(gain, unmute) + self._page1._configure_spk_pga(gain_db, unmute) @property - def speaker_mute(self): - """Get the speaker mute status.""" - reg_value = self._page1._read_register(REG_SPK_DRIVER) - return not bool(reg_value & (1 << 2)) # Inverse of unmute bit + def speaker_mute(self) -> bool: + """Get the speaker mute status. + + :return: True if speaker is muted, False otherwise + """ + reg_value = self._page1._read_register(_SPK_DRIVER) + return not bool(reg_value & (1 << 2)) @speaker_mute.setter - def speaker_mute(self, mute): - """Set the speaker mute status.""" + def speaker_mute(self, mute: bool) -> None: + """Set the speaker mute status. + + :param mute: True to mute speaker, False to unmute + """ gain = self.speaker_gain - self._page1._configure_spk_pga(gain, not mute) # Unmute is inverse of mute - + # Unmute is inverse of mute + self._page1._configure_spk_pga(gain, not mute) + @property - def dac_flags(self): - """Get the DAC and output driver status flags.""" + def dac_flags(self) -> DACFlags: + """Get the DAC and output driver status flags. + + :return: Dictionary with status flags + """ return self._page0._get_dac_flags() @property - def gpio1_mode(self): + def gpio1_mode(self) -> int: """Get the current GPIO1 pin mode. - - :return: The GPIO1 mode setting + + :return: One of the GPIO1_* mode constants """ - value = self._page0._read_register(REG_GPIO1_CTRL) + value = self._page0._read_register(_GPIO1_CTRL) return (value >> 2) & 0x0F @gpio1_mode.setter - def gpio1_mode(self, mode): + def gpio1_mode(self, mode: int) -> None: """Set the GPIO1 pin mode. - + :param mode: One of the GPIO1_* mode constants + :raises ValueError: If mode is not a valid GPIO1_* constant """ + valid_modes: List[int] = [ + GPIO1_DISABLED, + GPIO1_INPUT_MODE, + GPIO1_GPI, + GPIO1_GPO, + GPIO1_CLKOUT, + GPIO1_INT1, + GPIO1_INT2, + GPIO1_BCLK_OUT, + GPIO1_WCLK_OUT, + ] + + if mode not in valid_modes: + raise ValueError(f"Invalid GPIO1 mode: {mode}. Must be one of the GPIO1_* constants.") + self._page0._set_gpio1_mode(mode) @property - def din_input(self): - """Get the current DIN input value.""" + def din_input(self) -> int: + """Get the current DIN input value. + + :return: The DIN input value + """ return self._page0._get_din_input() @property - def codec_interface(self): - """Get the current codec interface settings.""" + def codec_interface(self) -> CodecInterface: + """Get the current codec interface settings. + + :return: Dictionary with codec interface settings + """ return self._page0._get_codec_interface() - + @property - def headphone_shorted(self): - """Check if headphone short circuit is detected.""" + def headphone_shorted(self) -> bool: + """Check if headphone short circuit is detected. + + :return: True if headphone is shorted, False otherwise + """ return self._page1._is_headphone_shorted() @property - def speaker_shorted(self): - """Check if speaker short circuit is detected.""" + def speaker_shorted(self) -> bool: + """Check if speaker short circuit is detected. + + :return: True if speaker is shorted, False otherwise + """ return self._page1._is_speaker_shorted() @property - def hpl_gain_applied(self): - """Check if all programmed gains have been applied to HPL.""" + def hpl_gain_applied(self) -> bool: + """Check if all programmed gains have been applied to HPL. + + :return: True if gains are applied, False otherwise + """ return self._page1._is_hpl_gain_applied() @property - def hpr_gain_applied(self): - """Check if all programmed gains have been applied to HPR.""" + def hpr_gain_applied(self) -> bool: + """Check if all programmed gains have been applied to HPR. + + :return: True if gains are applied, False otherwise + """ return self._page1._is_hpr_gain_applied() @property - def speaker_gain_applied(self): - """Check if all programmed gains have been applied to Speaker.""" + def speaker_gain_applied(self) -> bool: + """Check if all programmed gains have been applied to Speaker. + + :return: True if gains are applied, False otherwise + """ return self._page1._is_spk_gain_applied() @property - def headset_status(self): + def headset_status(self) -> int: """Get current headset detection status. - + :return: Integer value representing headset status (0=none, 1=without mic, 3=with mic) """ return self._page0._get_headset_status() - + @property - def reset_speaker_on_scd(self): + def reset_speaker_on_scd(self) -> bool: """Get the speaker reset behavior on short circuit detection. - + :return: True if speaker resets on short circuit, False otherwise """ - value = self._page1._read_register(REG_HP_SPK_ERR_CTL) - # Register bit is inverse of property (0 = reset, 1 = no reset) + value = self._page1._read_register(_HP_SPK_ERR_CTL) return not bool((value >> 1) & 0x01) @reset_speaker_on_scd.setter - def reset_speaker_on_scd(self, reset): + def reset_speaker_on_scd(self, reset: bool) -> None: """Configure speaker reset behavior on short circuit detection. - + :param reset: True to reset speaker on short circuit, False to remain unchanged """ self._page1._reset_speaker_on_scd(reset) @property - def reset_headphone_on_scd(self): + def reset_headphone_on_scd(self) -> bool: """Get the headphone reset behavior on short circuit detection. - + :return: True if headphone resets on short circuit, False otherwise """ - value = self._page1._read_register(REG_HP_SPK_ERR_CTL) - # Register bit is inverse of property (0 = reset, 1 = no reset) + value = self._page1._read_register(_HP_SPK_ERR_CTL) return not bool(value & 0x01) @reset_headphone_on_scd.setter - def reset_headphone_on_scd(self, reset): + def reset_headphone_on_scd(self, reset: bool) -> None: """Configure headphone reset behavior on short circuit detection. - + :param reset: True to reset headphone on short circuit, False to remain unchanged """ self._page1._reset_headphone_on_scd(reset) - def configure_headphone_pop(self, wait_for_powerdown=True, powerup_time=0x07, ramp_time=0x03): + def configure_headphone_pop( + self, wait_for_powerdown: bool = True, powerup_time: int = 0x07, ramp_time: int = 0x03 + ) -> bool: """Configure headphone pop removal settings. - + :param wait_for_powerdown: Wait for amp powerdown before DAC powerdown :param powerup_time: Driver power-on time (0-11) :param ramp_time: Driver ramp-up step time (0-3) - :return: True if successful + :return: True if successful, False otherwise """ return self._page1._configure_headphone_pop(wait_for_powerdown, powerup_time, ramp_time) @property - def speaker_wait_time(self): + def speaker_wait_time(self) -> int: """Get the current speaker power-up wait time. - + :return: The wait time setting (0-7) """ - value = self._page1._read_register(REG_PGA_RAMP) + value = self._page1._read_register(_PGA_RAMP) return (value >> 4) & 0x07 @speaker_wait_time.setter - def speaker_wait_time(self, wait_time): + def speaker_wait_time(self, wait_time: int) -> None: """Set speaker power-up wait time. - + :param wait_time: Speaker power-up wait duration (0-7) """ self._page1._set_speaker_wait_time(wait_time) @property - def headphone_lineout(self): + def headphone_lineout(self) -> bool: """Get the current headphone line-out configuration. - + :return: True if both channels are configured as line-out, False otherwise """ - # Read the register value - value = self._page1._read_register(REG_HP_DRIVER_CTRL) + value = self._page1._read_register(_HP_DRIVER_CTRL) left = bool(value & (1 << 2)) right = bool(value & (1 << 1)) - # Return True only if both channels are configured as line-out return left and right @headphone_lineout.setter - def headphone_lineout(self, enabled): + def headphone_lineout(self, enabled: bool) -> None: """Configure headphone outputs as line-out. - + :param enabled: True to configure both channels as line-out, False otherwise """ self._page1._headphone_lineout(enabled, enabled) - def config_mic_bias(self, power_down=False, always_on=False, voltage=0): + def config_mic_bias( + self, power_down: bool = False, always_on: bool = False, voltage: int = 0 + ) -> bool: """Configure MICBIAS settings. - + :param power_down: Enable software power down :param always_on: Keep MICBIAS on even without headset :param voltage: MICBIAS voltage setting (0-3) - :return: True if successful + :return: True if successful, False otherwise """ return self._page1._config_mic_bias(power_down, always_on, voltage) - def set_input_common_mode(self, ain1_cm, ain2_cm): + def set_input_common_mode(self, ain1_cm: bool, ain2_cm: bool) -> bool: """Set analog input common mode connections. - + :param ain1_cm: Connect AIN1 to common mode when unused :param ain2_cm: Connect AIN2 to common mode when unused - :return: True if successful + :return: True if successful, False otherwise """ return self._page1._set_input_common_mode(ain1_cm, ain2_cm) - def config_delay_divider(self, use_mclk=True, divider=1): + def config_delay_divider(self, use_mclk: bool = True, divider: int = 1) -> bool: """Configure programmable delay timer clock source and divider. - + :param use_mclk: True to use external MCLK, False for internal oscillator :param divider: Clock divider (1-127, or 0 for 128) - :return: True if successful + :return: True if successful, False otherwise """ return self._page3._config_delay_divider(use_mclk, divider) - + @property - def vol_adc_pin_control(self): - """Get the volume ADC pin control status.""" - reg_value = self._page0._read_register(REG_VOL_ADC_CTRL) + def vol_adc_pin_control(self) -> bool: + """Get the volume ADC pin control status. + + :return: True if volume ADC pin control is enabled, False otherwise + """ + reg_value = self._page0._read_register(_VOL_ADC_CTRL) return bool(reg_value & (1 << 7)) @vol_adc_pin_control.setter - def vol_adc_pin_control(self, enabled): - """Enable or disable volume ADC pin control.""" + def vol_adc_pin_control(self, enabled: bool) -> None: + """Enable or disable volume ADC pin control. + + :param enabled: True to enable volume ADC pin control, False to disable + """ current_config = self._get_vol_adc_config() self._page0._config_vol_adc( enabled, - current_config['use_mclk'], - current_config['hysteresis'], - current_config['rate'] + current_config["use_mclk"], + current_config["hysteresis"], + current_config["rate"], ) @property - def vol_adc_use_mclk(self): - """Get the volume ADC use MCLK status.""" - reg_value = self._page0._read_register(REG_VOL_ADC_CTRL) + def vol_adc_use_mclk(self) -> bool: + """Get the volume ADC use MCLK status. + + :return: True if volume ADC uses MCLK, False otherwise + """ + reg_value = self._page0._read_register(_VOL_ADC_CTRL) return bool(reg_value & (1 << 6)) @vol_adc_use_mclk.setter - def vol_adc_use_mclk(self, use_mclk): - """Set whether to use MCLK for volume ADC.""" + def vol_adc_use_mclk(self, use_mclk: bool) -> None: + """Set whether to use MCLK for volume ADC. + + :param use_mclk: True to use MCLK, False to use internal oscillator + """ current_config = self._get_vol_adc_config() self._page0._config_vol_adc( - current_config['pin_control'], + current_config["pin_control"], use_mclk, - current_config['hysteresis'], - current_config['rate'] + current_config["hysteresis"], + current_config["rate"], ) @property - def vol_adc_hysteresis(self): - """Get the volume ADC hysteresis setting.""" - reg_value = self._page0._read_register(REG_VOL_ADC_CTRL) + def vol_adc_hysteresis(self) -> int: + """Get the volume ADC hysteresis setting. + + :return: Hysteresis value (0-3) + """ + reg_value = self._page0._read_register(_VOL_ADC_CTRL) return (reg_value >> 4) & 0x03 @vol_adc_hysteresis.setter - def vol_adc_hysteresis(self, hysteresis): - """Set the volume ADC hysteresis.""" + def vol_adc_hysteresis(self, hysteresis: int) -> None: + """Set the volume ADC hysteresis. + + :param hysteresis: Hysteresis value (0-3) + """ current_config = self._get_vol_adc_config() self._page0._config_vol_adc( - current_config['pin_control'], - current_config['use_mclk'], + current_config["pin_control"], + current_config["use_mclk"], hysteresis, - current_config['rate'] + current_config["rate"], ) @property - def vol_adc_rate(self): - """Get the volume ADC sampling rate.""" - reg_value = self._page0._read_register(REG_VOL_ADC_CTRL) + def vol_adc_rate(self) -> int: + """Get the volume ADC sampling rate. + + :return: Rate value (0-7) + """ + reg_value = self._page0._read_register(_VOL_ADC_CTRL) return reg_value & 0x07 @vol_adc_rate.setter - def vol_adc_rate(self, rate): - """Set the volume ADC sampling rate.""" + def vol_adc_rate(self, rate: int) -> None: + """Set the volume ADC sampling rate. + + :param rate: Rate value (0-7) + """ current_config = self._get_vol_adc_config() self._page0._config_vol_adc( - current_config['pin_control'], - current_config['use_mclk'], - current_config['hysteresis'], - rate + current_config["pin_control"], + current_config["use_mclk"], + current_config["hysteresis"], + rate, ) - def _get_vol_adc_config(self): - """Helper method to get the current volume ADC configuration.""" - reg_value = self._page0._read_register(REG_VOL_ADC_CTRL) + def _get_vol_adc_config(self) -> Dict[str, Any]: + """Helper method to get the current volume ADC configuration. + + :return: Dictionary with current volume ADC configuration + """ + reg_value = self._page0._read_register(_VOL_ADC_CTRL) return { - 'pin_control': bool(reg_value & (1 << 7)), - 'use_mclk': bool(reg_value & (1 << 6)), - 'hysteresis': (reg_value >> 4) & 0x03, - 'rate': reg_value & 0x07 + "pin_control": bool(reg_value & (1 << 7)), + "use_mclk": bool(reg_value & (1 << 6)), + "hysteresis": (reg_value >> 4) & 0x03, + "rate": reg_value & 0x07, } @property - def vol_adc_db(self): - """Read the current volume from the Volume ADC in dB.""" + def vol_adc_db(self) -> float: + """Read the current volume from the Volume ADC in dB. + + :return: Volume in dB + """ return self._page0._read_vol_adc_db() - def int2_sources(self, headset_detect=False, button_press=False, dac_drc=False, - agc_noise=False, over_current=False, multiple_pulse=False): + def int2_sources( + self, + headset_detect: bool = False, + button_press: bool = False, + dac_drc: bool = False, + agc_noise: bool = False, + over_current: bool = False, + multiple_pulse: bool = False, + ) -> bool: """Configure the INT2 interrupt sources. - + :param headset_detect: Enable headset detection interrupt :param button_press: Enable button press detection interrupt :param dac_drc: Enable DAC DRC signal power interrupt :param agc_noise: Enable DAC data overflow interrupt :param over_current: Enable short circuit interrupt :param multiple_pulse: If true, INT2 generates multiple pulses until flag read - :return: True if successful + :return: True if successful, False otherwise """ - return self._page0._set_int2_source(headset_detect, button_press, dac_drc, - agc_noise, over_current, multiple_pulse) - - def configure_clocks(self, mclk_freq, sample_rate, bit_depth=16): + return self._page0._set_int2_source( + headset_detect, button_press, dac_drc, agc_noise, over_current, multiple_pulse + ) + + def configure_clocks(self, mclk_freq: int, sample_rate: int, bit_depth: int = 16) -> bool: """Configure the TLV320DAC3100 clock settings. - - This function configures all necessary clock settings including PLL, dividers, + + This function configures all necessary clock settings including MCLK, dividers, and interface settings to achieve the requested sample rate with the provided - master clock frequency. - - :param mclk_freq: The master clock frequency in Hz (e.g., 12000000 for 12MHz) + main clock frequency. + + :param mclk_freq: The main clock frequency in Hz (e.g., 12000000 for 12MHz) :param sample_rate: The desired sample rate in Hz (e.g., 44100, 48000) :param bit_depth: The bit depth (16, 20, 24, or 32) :return: True if successful, False otherwise """ - # Store these values for later retrieval self._mclk_freq = mclk_freq self._sample_rate = sample_rate self._bit_depth = bit_depth - return self._page0._configure_clocks_for_sample_rate(mclk_freq, sample_rate, bit_depth) @property - def audio_preset(self): - """Get the current audio preset based on sample rate and bit depth. - - :return: One of the PRESET_* constants, or None if no matching preset - """ - if self._sample_rate == 22050 and self._bit_depth == 16: - return PRESET_MID_QUALITY - elif self._sample_rate == 44100 and self._bit_depth == 16: - return PRESET_CD_QUALITY - elif self._sample_rate == 48000 and self._bit_depth == 24: - return PRESET_DVD_QUALITY - elif self._sample_rate == 96000 and self._bit_depth == 24: - return PRESET_HIRES_QUALITY - else: - return None + def headphone_output(self) -> bool: + """Headphone output helper with quickstart settings for users. + Get headphone output state (True if either left or right channel is powered). - @audio_preset.setter - def audio_preset(self, preset): - """Configure the DAC for common audio presets. - - :param preset: One of the PRESET_* constants + :return: True if headphone output is enabled, False otherwise """ - if preset == PRESET_MID_QUALITY: - # CircuitPython synth: 22.05kHz, 16-bit - self._sample_rate = 22050 - self._bit_depth = 16 - self.configure_clocks(12000000, 22050, 16) - elif preset == PRESET_CD_QUALITY: - # CD quality: 44.1kHz, 16-bit - self._sample_rate = 44100 - self._bit_depth = 16 - self.configure_clocks(12000000, 44100, 16) - elif preset == PRESET_DVD_QUALITY: - # DVD quality: 48kHz, 24-bit - self._sample_rate = 48000 - self._bit_depth = 24 - self.configure_clocks(12000000, 48000, 24) - elif preset == PRESET_HIRES_QUALITY: - # High-res audio: 96kHz, 24-bit - self._sample_rate = 96000 - self._bit_depth = 24 - self.configure_clocks(12000000, 96000, 24) - - @property - def headphone_output(self): - """Get headphone output state (True if either left or right channel is powered).""" - hp_drivers = self._page1._read_register(REG_HP_DRIVERS) + hp_drivers = self._page1._read_register(_HP_DRIVERS) left_powered = bool(hp_drivers & (1 << 7)) right_powered = bool(hp_drivers & (1 << 6)) return left_powered or right_powered @headphone_output.setter - def headphone_output(self, enabled): - """Enable or disable headphone outputs.""" + def headphone_output(self, enabled: bool) -> None: + """Headphone output helper with quickstart settings for users. + Enable or disable headphone outputs. + + :param enabled: True to enable headphone output, False to disable + """ if enabled: - # Configure headphone drivers - power both channels with common mode voltage self._page1._configure_headphone_driver( - left_powered=True, - right_powered=True, - common=HP_COMMON_1_65V # Try a higher common-mode voltage + left_powered=True, right_powered=True, common=HP_COMMON_1_65V ) - - # Make sure HP driver registers are properly configured - self._page1._configure_hpl_pga(gain_db=0, unmute=True) - self._page1._configure_hpr_pga(gain_db=0, unmute=True) - - # Route DAC to headphone outputs - self._page1._configure_analog_inputs( - left_dac=DAC_ROUTE_HP, - right_dac=DAC_ROUTE_HP - ) - - # Enable DAC data path - self._page0._set_dac_data_path( - left_dac_on=True, - right_dac_on=True, - left_path=DAC_PATH_NORMAL, - right_path=DAC_PATH_NORMAL - ) - - # Set reasonable initial volume if not already set - self._page1._set_hpl_volume(route_enabled=True, gain=50) # Use a higher gain value - self._page1._set_hpr_volume(route_enabled=True, gain=50) # Use a higher gain value - - # Ensure DAC volume control is properly set - self._page0._set_dac_volume_control(False, False, VOL_INDEPENDENT) - - # Ensure adequate DAC volume - self._page0._set_channel_volume(False, 0) # Left channel at 0dB - self._page0._set_channel_volume(True, 0) # Right channel at 0dB + self.headphone_left_gain = 0 + self.headphone_right_gain = 0 + self.headphone_left_mute = False + self.headphone_right_mute = False + self._page1._configure_analog_inputs(left_dac=DAC_ROUTE_HP, right_dac=DAC_ROUTE_HP) + self.left_dac = True + self.right_dac = True + self.left_dac_path = DAC_PATH_NORMAL + self.right_dac_path = DAC_PATH_NORMAL + self.headphone_volume = 0 + self.dac_volume_control_mode = VOL_INDEPENDENT + self.left_dac_mute = False + self.right_dac_mute = False + self.left_dac_channel_volume = 0 + self.right_dac_channel_volume = 0 else: - # Power down headphone drivers self._page1._configure_headphone_driver(left_powered=False, right_powered=False) - - # Unroute DAC from headphone outputs - self._page1._configure_analog_inputs( - left_dac=DAC_ROUTE_NONE, - right_dac=DAC_ROUTE_NONE - ) + self._page1._configure_analog_inputs(left_dac=DAC_ROUTE_NONE, right_dac=DAC_ROUTE_NONE) @property - def speaker_output(self): - """Get speaker output state.""" + def speaker_output(self) -> bool: + """Speaker output helper with quickstart settings for users. + Get speaker output state. + + :return: True if speaker output is enabled, False otherwise + """ return self._page1._get_speaker_enabled() @speaker_output.setter - def speaker_output(self, enabled): - """Enable or disable speaker output. - - This is a high-level function that: - 1. Powers on/off the speaker amplifier - 2. Sets appropriate volume and PGA settings when enabled - + def speaker_output(self, enabled: bool) -> None: + """Speaker output helper with quickstart settings for users. + Enable or disable speaker output. + :param enabled: True to enable speaker, False to disable """ if enabled: - # Enable speaker amplifier self._page1._set_speaker_enabled(True) - - # Route DAC to speaker (via mixer) self._page1._configure_analog_inputs( - left_dac=DAC_ROUTE_MIXER, - right_dac=DAC_ROUTE_MIXER + left_dac=DAC_ROUTE_MIXER, right_dac=DAC_ROUTE_MIXER ) - - # Enable DAC data path if not already enabled - self._page0._set_dac_data_path( - left_dac_on=True, - right_dac_on=True - ) - - # Set reasonable initial volume and unmute - self._page1._set_spk_volume(route_enabled=True, gain=20) - self._page1._configure_spk_pga(gain=SPK_GAIN_6DB, unmute=True) + self.left_dac = True + self.right_dac = True + self.speaker_volume = -10 + self.speaker_gain = SPK_GAIN_6DB + self.speaker_mute = False else: - # Disable speaker amplifier self._page1._set_speaker_enabled(False) @property - def headphone_volume(self): + def headphone_volume(self) -> float: """Get the current headphone volume in dB. - + :return: The volume in dB (0 = max, -63.5 = min) """ - # Since HPL and HPR might have different values, we'll average them - # Alternatively, you could decide to just return one channel - left_gain = self._page1._read_register(REG_HPL_VOL) & 0x7F - right_gain = self._page1._read_register(REG_HPR_VOL) & 0x7F - + left_gain = self._page1._read_register(_HPL_VOL) & 0x7F + right_gain = self._page1._read_register(_HPR_VOL) & 0x7F avg_gain = (left_gain + right_gain) / 2 # Convert from register value to dB # 55 ≈ 0dB, 0 ≈ -63.5dB db = (avg_gain - 55) / 1.14 - return db @headphone_volume.setter - def headphone_volume(self, db): + def headphone_volume(self, db: float) -> None: """Set headphone volume in dB. - + :param db: Volume in dB (0 = max, -63.5 = min) """ # Convert from dB to register gain value (0-127) # 0dB = ~55, -63.5dB = 0 if db > 0: db = 0 # Limit to 0dB to prevent distortion - - # Scale to analog volume value (approximate conversion) gain = int(55 + (db * 1.14)) - - # Constrain to valid range gain = max(0, min(gain, 127)) - - # Set volume for both channels self._page1._set_hpl_volume(route_enabled=True, gain=gain) self._page1._set_hpr_volume(route_enabled=True, gain=gain) @property - def speaker_volume(self): + def speaker_volume(self) -> float: """Get the current speaker volume in dB. - + :return: The volume in dB (0 = max, -63.5 = min) """ - gain = self._page1._read_register(REG_SPK_VOL) & 0x7F - + gain = self._page1._read_register(_SPK_VOL) & 0x7F # Convert from register value to dB # 55 ≈ 0dB, 0 ≈ -63.5dB db = (gain - 55) / 1.14 - return db @speaker_volume.setter - def speaker_volume(self, db): + def speaker_volume(self, db: float) -> None: """Set speaker volume in dB. - + :param db: Volume in dB (0 = max, -63.5 = min) """ - # Convert from dB to register gain value (similar to headphone) if db > 0: - db = 0 # Limit to 0dB to prevent distortion - - # Scale to analog volume value (approximate conversion) + db = 0 gain = int(55 + (db * 1.14)) - - # Constrain to valid range gain = max(0, min(gain, 127)) - self._page1._set_spk_volume(route_enabled=True, gain=gain) - + @property - def sample_rate(self): - """Get the configured sample rate in Hz.""" + def sample_rate(self) -> int: + """Configured sample rate in Hz. + + :return: The sample rate in Hz + """ return self._sample_rate @property - def bit_depth(self): - """Get the configured bit depth.""" + def bit_depth(self) -> int: + """Configured bit depth. + + :return: The bit depth + """ return self._bit_depth - + @property - def mclk_freq(self): - """Get the configured MCLK frequency in Hz.""" + def mclk_freq(self) -> int: + """Configured MCLK frequency in Hz. + + :return: The MCLK frequency in Hz + """ return self._mclk_freq diff --git a/examples/tlv320_fulltest.py b/examples/tlv320_fulltest.py index 1c0eeb2..65f2c70 100644 --- a/examples/tlv320_fulltest.py +++ b/examples/tlv320_fulltest.py @@ -1,217 +1,253 @@ -# SPDX-FileCopyrightText: 2024 Your Name +# SPDX-FileCopyrightText: 2025 Liz Clark for Adafruit Industries +# # SPDX-License-Identifier: MIT """ -Comprehensive test for TLV320DAC3100 CircuitPython driver. -Tests all implemented functionality and plays a sine wave. +Full TLV320DAC3100 Test +Demo all features in the library """ import time -import array -import math -import audiocore + import board -import busio -import audiobusio -import adafruit_tlv320 -def print_test_header(name): - """Print a test section header.""" - print("\n" + "="*40) - print(f"Testing {name}") - print("="*40) +from adafruit_tlv320 import ( + DAC_PATH_MIXED, + DAC_PATH_NORMAL, + DAC_PATH_OFF, + DAC_PATH_SWAPPED, + DAC_ROUTE_HP, + DAC_ROUTE_MIXER, + DAC_ROUTE_NONE, + GPIO1_CLKOUT, + GPIO1_DISABLED, + GPIO1_GPI, + GPIO1_GPO, + GPIO1_INPUT_MODE, + HP_COMMON_1_35V, + HP_COMMON_1_50V, + HP_COMMON_1_65V, + HP_COMMON_1_80V, + SPK_GAIN_6DB, + SPK_GAIN_12DB, + SPK_GAIN_18DB, + SPK_GAIN_24DB, + TLV320DAC3100, + VOL_INDEPENDENT, + VOL_LEFT_TO_RIGHT, + VOL_RIGHT_TO_LEFT, +) -def test_result(name, result): - """Print result of a test.""" - if result: - print(f"✓ {name}: Success") - else: - print(f"✗ {name}: Failure") - return result - -# Initialize I2C bus -print("Initializing I2C...") -i2c = busio.I2C(board.SCL, board.SDA) - -# Initialize the TLV320DAC3100 -print("Initializing TLV320DAC3100...") -try: - tlv = adafruit_tlv320.TLV320DAC3100(i2c) - print("DAC initialized successfully!") -except Exception as e: - print(f"Failed to initialize TLV320DAC3100: {e}") - raise +print("Initializing I2C and TLV320DAC3100...") +i2c = board.I2C() +dac = TLV320DAC3100(i2c) # Reset the device -print_test_header("Reset") -test_result("Reset DAC", tlv.reset()) +print("Resetting the DAC...") +dac.reset() +time.sleep(0.1) # Give device time to reset -# Test basic health functions -print_test_header("Basic Health") -print(f"Overtemperature status: {'ALERT!' if tlv.overtemperature else 'OK'}") +# Display basic information +print("\n=== Basic Information ===") +print(f"Sample rate: {dac.sample_rate} Hz") +print(f"Bit depth: {dac.bit_depth}-bit") +print(f"MCLK frequency: {dac.mclk_freq} Hz") +print(f"Overtemperature condition: {dac.overtemperature}") -# Test Clock Configuration -print_test_header("Clock Configuration") +# I2S Config +dac.configure_clocks(mclk_freq=12000000, sample_rate=22050, bit_depth=16) +print(f"Sample rate: {dac.sample_rate} Hz") +print(f"Bit depth: {dac.bit_depth}-bit") +print(f"MCLK frequency: {dac.mclk_freq} Hz") +time.sleep(0.2) +dac.configure_clocks(mclk_freq=12000000, sample_rate=48000, bit_depth=32) +print(f"Sample rate: {dac.sample_rate} Hz") +print(f"Bit depth: {dac.bit_depth}-bit") +print(f"MCLK frequency: {dac.mclk_freq} Hz") +time.sleep(0.2) -# Set PLL input and codec input -tlv.pll_clock_input = adafruit_tlv320.PLL_CLKIN_BCLK -tlv.codec_clock_input = adafruit_tlv320.CODEC_CLKIN_PLL -print(f"PLL clock input set to: {tlv.pll_clock_input}") -print(f"CODEC clock input set to: {tlv.codec_clock_input}") +# Headphone Output Setup +print("\n=== Headphone Output Setup ===") +print("Setting up headphone output...") +dac.headphone_output = True # This conveniently sets up multiple parameters +print(f"Headphone output enabled: {dac.headphone_output}") -# Configure clock divider input -tlv.clock_divider_input = adafruit_tlv320.CDIV_CLKIN_PLL -print(f"Clock divider input: {tlv.clock_divider_input}") +# Adjust headphone volume (in dB where 0dB is max, negative values reduce volume) +print("\nAdjusting headphone volume...") +print(f"Current headphone volume: {dac.headphone_volume} dB") +dac.headphone_volume = -10 # Set to -10 dB (moderate volume) +print(f"New headphone volume: {dac.headphone_volume} dB") -# Set PLL values -test_result("Set PLL values", tlv.set_pll_values(1, 1, 7, 6144)) -pll_vals = tlv.get_pll_values() -print(f"PLL values: P={pll_vals[0]}, R={pll_vals[1]}, J={pll_vals[2]}, D={pll_vals[3]}") +# Fine-tune left and right channels individually +print("\nAdjusting left and right headphone gain...") +print(f"Left headphone gain: {dac.headphone_left_gain}") +print(f"Right headphone gain: {dac.headphone_right_gain}") +dac.headphone_left_gain = 3 # Increase left channel gain +dac.headphone_right_gain = 3 # Increase right channel gain +print(f"New left headphone gain: {dac.headphone_left_gain}") +print(f"New right headphone gain: {dac.headphone_right_gain}") -# Set DAC clock dividers -test_result("Set NDAC", tlv.set_ndac(True, 4)) -ndac_vals = tlv.get_ndac() -print(f"NDAC: enabled={ndac_vals[0]}, value={ndac_vals[1]}") +# Mute/unmute the headphones +print("\nDemonstrating headphone mute functionality...") +print(f"Left headphone muted: {dac.headphone_left_mute}") +print(f"Right headphone muted: {dac.headphone_right_mute}") +dac.headphone_left_mute = True # Mute left channel +dac.headphone_right_mute = False # Ensure right channel is unmuted +print(f"Left headphone now muted: {dac.headphone_left_mute}") +print(f"Right headphone still unmuted: {dac.headphone_right_mute}") +time.sleep(1) # Listen to right channel only +dac.headphone_left_mute = False # Unmute left channel +print(f"Left headphone now unmuted: {dac.headphone_left_mute}") +dac.headphone_output = False # turn off before speaker test -test_result("Set MDAC", tlv.set_mdac(True, 1)) -mdac_vals = tlv.get_mdac() -print(f"MDAC: enabled={mdac_vals[0]}, value={mdac_vals[1]}") +# Speaker Output Setup +print("\n=== Speaker Output Setup ===") +print("Setting up speaker output...") +dac.speaker_output = True # This conveniently sets up multiple parameters +print(f"Speaker output enabled: {dac.speaker_output}") -test_result("Set DOSR", tlv.set_dosr(256)) -dosr_val = tlv.get_dosr() -print(f"DOSR value: {dosr_val}") +# Adjust speaker volume (in dB where 0dB is max, negative values reduce volume) +print("\nAdjusting speaker volume...") +print(f"Current speaker volume: {dac.speaker_volume} dB") +dac.speaker_volume = -6 # Set to -6 dB (louder than headphones) +print(f"New speaker volume: {dac.speaker_volume} dB") -# Power up the PLL -tlv.pll_power = True -print(f"PLL power state: {'ON' if tlv.pll_power else 'OFF'}") +# Set speaker amplifier gain +print("\nAdjusting speaker gain...") +print(f"Current speaker gain: {dac.speaker_gain}") +dac.speaker_gain = SPK_GAIN_12DB # 12dB amplification +print(f"New speaker gain: {dac.speaker_gain}") -# Test GPIO and interrupt configuration -print_test_header("GPIO and Interrupts") +# Mute/unmute the speaker +print("\nDemonstrating speaker mute functionality...") +print(f"Speaker muted: {dac.speaker_mute}") +dac.speaker_mute = True # Mute speaker +print(f"Speaker now muted: {dac.speaker_mute}") +time.sleep(1) +dac.speaker_mute = False # Unmute speaker +print(f"Speaker now unmuted: {dac.speaker_mute}") -test_result("Set GPIO1 mode", tlv.set_gpio1_mode(adafruit_tlv320.GPIO1_INT1)) -test_result("Set INT1 sources", tlv.set_int1_source(True, False, False, False, False, False)) -test_result("Set INT2 sources", tlv.set_int2_source(True, False, False, False, False, False)) -print(f"GPIO1 input state: {tlv.get_gpio1_input()}") -print(f"DIN input state: {tlv.get_din_input()}") +# DAC Signal Routing +print("\n=== Advanced DAC Signal Routing ===") +print("\nCurrent DAC Status:") +print(f"Left DAC enabled: {dac.left_dac}") +print(f"Right DAC enabled: {dac.right_dac}") +print(f"Left DAC path: {dac.left_dac_path}") +print(f"Right DAC path: {dac.right_dac_path}") -# Test codec interface configuration -print_test_header("Codec Interface") +print("\nSetting up swapped stereo (left and right channels swapped)...") +dac.left_dac_path = DAC_PATH_SWAPPED +dac.right_dac_path = DAC_PATH_SWAPPED +print(f"New left DAC path: {dac.left_dac_path}") +print(f"New right DAC path: {dac.right_dac_path}") -test_result("Set codec interface", tlv.set_codec_interface(adafruit_tlv320.FORMAT_I2S, adafruit_tlv320.DATA_LEN_16)) -codec_if = tlv.get_codec_interface() -print(f"Codec interface: {codec_if}") +print("\nSetting up mono output (mixed left and right channels)...") +dac.left_dac_path = DAC_PATH_MIXED +dac.right_dac_path = DAC_PATH_MIXED +print(f"New left DAC path: {dac.left_dac_path}") +print(f"New right DAC path: {dac.right_dac_path}") -# Test DAC path configuration -print_test_header("DAC Configuration") +print("\nRestoring normal stereo...") +dac.left_dac_path = DAC_PATH_NORMAL +dac.right_dac_path = DAC_PATH_NORMAL +print(f"New left DAC path: {dac.left_dac_path}") +print(f"New right DAC path: {dac.right_dac_path}") -test_result("Set DAC data path", tlv.set_dac_data_path(True, True)) -dac_path = tlv.get_dac_data_path() -print(f"DAC data path: {dac_path}") +# DAC Volume Controls +print("\n=== DAC Volume Control Configuration ===") +print(f"Left DAC muted: {dac.left_dac_mute}") +print(f"Right DAC muted: {dac.right_dac_mute}") +print(f"Volume control mode: {dac.dac_volume_control_mode}") -test_result("Set DAC volume control", tlv.set_dac_volume_control(False, False, adafruit_tlv320.VOL_INDEPENDENT)) -vol_ctrl = tlv.get_dac_volume_control() -print(f"DAC volume control: {vol_ctrl}") +print("\nSetting volume control mode where left channel controls right...") +dac.dac_volume_control_mode = VOL_LEFT_TO_RIGHT +print(f"New volume control mode: {dac.dac_volume_control_mode}") -test_result("Set left channel volume", tlv.set_channel_volume(False, 0)) -left_vol = tlv.get_channel_volume(False) -print(f"Left DAC volume: {left_vol} dB") +print("\nSetting independent volume control mode (default)...") +dac.dac_volume_control_mode = VOL_INDEPENDENT +print(f"New volume control mode: {dac.dac_volume_control_mode}") -test_result("Set right channel volume", tlv.set_channel_volume(True, 0)) -right_vol = tlv.get_channel_volume(True) -print(f"Right DAC volume: {right_vol} dB") +# DAC Channel Volume +print("\n=== DAC Channel Volume ===") +print(f"Left DAC channel volume: {dac.left_dac_channel_volume} dB") +print(f"Right DAC channel volume: {dac.right_dac_channel_volume} dB") -# Test headphone and speaker configuration -print_test_header("Headphone and Speaker") +print("\nSetting different volumes for left and right channels...") +dac.left_dac_channel_volume = -3 +dac.right_dac_channel_volume = -9 +print(f"New left DAC channel volume: {dac.left_dac_channel_volume} dB") +print(f"New right DAC channel volume: {dac.right_dac_channel_volume} dB") -test_result("Configure headphone driver", tlv.configure_headphone_driver(True, True)) -test_result("Configure analog inputs", tlv.configure_analog_inputs(adafruit_tlv320.DAC_ROUTE_HP, adafruit_tlv320.DAC_ROUTE_HP)) +# Headphone as Line-Out +print("\n=== Configure Headphone as Line-Out ===") +print(f"Headphone configured as line-out: {dac.headphone_lineout}") -test_result("Set HPL volume", tlv.set_hpl_volume(True, 20)) -test_result("Configure HPL PGA", tlv.configure_hpl_pga(9, True)) -print(f"HPL gain applied: {tlv.is_hpl_gain_applied()}") +# Safety Features +print("\n=== Safety Features ===") +print(f"Reset speaker on short circuit: {dac.reset_speaker_on_scd}") +print(f"Reset headphone on short circuit: {dac.reset_headphone_on_scd}") -test_result("Set HPR volume", tlv.set_hpr_volume(True, 20)) -test_result("Configure HPR PGA", tlv.configure_hpr_pga(9, True)) -print(f"HPR gain applied: {tlv.is_hpr_gain_applied()}") +# Getting status flags +print("\n=== Status Information ===") +flags = dac.dac_flags +print(f"Left DAC powered: {flags['left_dac_powered']}") +print(f"Right DAC powered: {flags['right_dac_powered']}") +print(f"Headphone left (HPL) powered: {flags['hpl_powered']}") +print(f"Headphone right (HPR) powered: {flags['hpr_powered']}") +print(f"Left Class-D amplifier powered: {flags['left_classd_powered']}") +print(f"Right Class-D amplifier powered: {flags['right_classd_powered']}") +print(f"Left PGA gain OK: {flags['left_pga_gain_ok']}") +print(f"Right PGA gain OK: {flags['right_pga_gain_ok']}") -tlv.speaker_enabled = True -print(f"Speaker enabled: {tlv.speaker_enabled}") -test_result("Configure SPK PGA", tlv.configure_spk_pga(adafruit_tlv320.SPK_GAIN_6DB, True)) -test_result("Set SPK volume", tlv.set_spk_volume(False, 20)) -print(f"SPK gain applied: {tlv.is_spk_gain_applied()}") -print(f"Speaker shorted: {tlv.is_speaker_shorted()}") +# Additional status checks via dedicated properties +print("\nStatus via dedicated properties:") +print(f"Speaker shorted: {dac.speaker_shorted}") +print(f"HPL gain fully applied: {dac.hpl_gain_applied}") +print(f"HPR gain fully applied: {dac.hpr_gain_applied}") +print(f"Speaker gain fully applied: {dac.speaker_gain_applied}") -# Test headset detection -print_test_header("Headset Detection") +# Higher-level shortcut methods +print("\n=== Using Higher-Level Shortcuts ===") +print("Demonstrating on/off control of primary outputs:") -test_result("Set headset detect", tlv.set_headset_detect(True)) -headset_status = tlv.get_headset_status() -print(f"Headset status: {headset_status}") +print("\nTurning off headphone output...") +dac.headphone_output = False +print(f"Headphone output now: {dac.headphone_output}") -# Test hardware configuration -print_test_header("Hardware Configuration") +print("\nTurning off speaker output...") +dac.speaker_output = True +print(f"Speaker output now: {dac.speaker_output}") -test_result("Reset speaker on SCD", tlv.reset_speaker_on_scd(True)) -test_result("Reset headphone on SCD", tlv.reset_headphone_on_scd(True)) -test_result("Configure headphone pop", tlv.configure_headphone_pop(True, 0x07, 0x03)) -test_result("Set speaker wait time", tlv.set_speaker_wait_time(0x02)) -test_result("Configure headphone as lineout", tlv.headphone_lineout(False, False)) -test_result("Configure mic bias", tlv.config_mic_bias(False, False, 0x01)) -test_result("Set input common mode", tlv.set_input_common_mode(True, True)) -test_result("Configure delay divider", tlv.config_delay_divider(True, 1)) +print("\nSwapping outputs...") +dac.speaker_output = False +dac.headphone_output = True +print(f"Headphone output now: {dac.headphone_output}") +print(f"Speaker output now: {dac.speaker_output}") -# Test Volume ADC -print_test_header("Volume ADC") +# Pop Removal Setting +print("\n=== Headphone Pop Removal Settings ===") +dac.configure_headphone_pop( + wait_for_powerdown=True, # Wait for amp powerdown before DAC powerdown + powerup_time=7, # Power-on time setting (0-11) + ramp_time=3, # Ramp-up step time (0-3) +) +print("Headphone pop removal configured") -test_result("Configure Volume ADC", tlv.config_vol_adc(False, True, 0, 0)) -vol_adc = tlv.read_vol_adc_db() -print(f"Volume ADC reading: {vol_adc} dB") +# External Settings (like GPIO) +print("\n=== GPIO Configuration ===") +print(f"Current GPIO1 mode: {dac.gpio1_mode}") +dac.gpio1_mode = GPIO1_GPO # Set GPIO1 as general purpose output +print(f"New GPIO1 mode: {dac.gpio1_mode}") +dac.gpio1_mode = GPIO1_DISABLED # Disable GPIO1 +print(f"Disabled GPIO1 mode: {dac.gpio1_mode}") -# Get DAC flags -print_test_header("DAC Status Flags") +# Headset Detection +print("\n=== Headset Detection ===") +print(f"Current headset status: {dac.headset_status}") +# 0 = none, 1 = without mic, 3 = with mic -dac_flags = tlv.get_dac_flags() -print("DAC Flags:") -for key, value in dac_flags.items(): - print(f" {key}: {value}") +# Volume Control ADC +print("\n=== Volume Control ADC ===") +print(f"Volume ADC reading: {dac.vol_adc_db} dB") -# Initialize I2S for audio playback -print_test_header("Audio Playback") -print("Initializing I2S...") - -try: - # BCLK, WCLK/LRCLK, DATA - adjust pins as needed for your board - audio = audiobusio.I2SOut(board.I2S_BCLK, board.I2S_WS, board.I2S_DIN) - - # Generate a sine wave - tone_volume = 0.5 # Higher volume - frequency = 440 # 440 Hz tone (A4) - sample_rate = 44100 # CD-quality sample rate - length = sample_rate // frequency - sine_wave = array.array("h", [0] * length) - for i in range(length): - sine_wave[i] = int((math.sin(math.pi * 2 * i / length)) * tone_volume * (2 ** 15 - 1)) - - sine_wave_sample = audiocore.RawSample(sine_wave, sample_rate=sample_rate) - - print("Starting audio playback...") - - # Continuous playback - audio.play(sine_wave_sample, loop=True) - - # Main loop - print("All tests completed! Playing tone continuously.") - print("Press Ctrl+C to stop.") - - while True: - # Check for overtemperature every 5 seconds - if tlv.overtemperature: - print("WARNING: DAC is overheating!") - time.sleep(5) - -except Exception as e: - print(f"Audio error: {e}") - - # If audio setup fails, just loop - while True: - time.sleep(1) +print("\nAll examples completed!") diff --git a/examples/tlv320_simpletest.py b/examples/tlv320_simpletest.py index 55aac92..9754462 100644 --- a/examples/tlv320_simpletest.py +++ b/examples/tlv320_simpletest.py @@ -1,40 +1,40 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025 Liz Clark for Adafruit Industries +# SPDX-FileCopyrightText: 2025 Liz Clark for Adafruit Industries +# # SPDX-License-Identifier: MIT -import time import array import math +import time + +import audiobusio import audiocore import board import busio -import audiobusio + import adafruit_tlv320 i2c = busio.I2C(board.SCL, board.SDA) dac = adafruit_tlv320.TLV320DAC3100(i2c) -# use a preset -# (PRESET_MID_QUALITY, PRESET_CD_QUALITY, PRESET_DVD_QUALITY or PRESET_HIRES_QUALITY) -# dac.configure_audio_preset(adafruit_tlv320.PRESET_CD_QUALITY)) -# or set mclk, sample rate & bit depth manually -dac.configure_clocks(mclk_freq=12000000, sample_rate=22050, bit_depth=8) +# set mclk, sample rate & bit depth +dac.configure_clocks(mclk_freq=12000000, sample_rate=44100, bit_depth=16) # use headphones dac.headphone_output = True -dac.headphone_volume = -20 +dac.headphone_volume = -20 # dB # or use speaker # dac.speaker_output = True -# dac.speaker_volume = -15 +# dac.speaker_volume = -15 # dB audio = audiobusio.I2SOut(board.I2S_BCLK, board.I2S_WS, board.I2S_DIN) -# Generate a sine wave +# generate a sine wave tone_volume = 0.5 frequency = 440 sample_rate = dac.sample_rate length = sample_rate // frequency sine_wave = array.array("h", [0] * length) for i in range(length): - sine_wave[i] = int((math.sin(math.pi * 2 * i / length)) * tone_volume * (2 ** 15 - 1)) + sine_wave[i] = int((math.sin(math.pi * 2 * i / length)) * tone_volume * (2**15 - 1)) sine_wave_sample = audiocore.RawSample(sine_wave, sample_rate=sample_rate) while True: diff --git a/examples/tlv320_sinetone.py b/examples/tlv320_sinetone.py deleted file mode 100644 index 3b95acc..0000000 --- a/examples/tlv320_sinetone.py +++ /dev/null @@ -1,107 +0,0 @@ -# SPDX-FileCopyrightText: 2024 Liz Clark for Adafruit Industries -# SPDX-License-Identifier: MIT - -""" -CircuitPython TLV320DAC3100 I2S Tone playback example. -Configures the TLV320DAC3100 DAC via I2C, then plays a tone -for one second on, one second off, in a loop. -""" -import time -import array -import math -import audiocore -import board -import busio -import audiobusio -import adafruit_tlv320 - -# Initialize I2C for DAC configuration -i2c = busio.I2C(board.SCL, board.SDA) - -# Initialize and configure the TLV320DAC3100 -print("Initializing TLV320DAC3100...") -tlv = adafruit_tlv320.TLV320DAC3100(i2c) - -# Reset configuration -tlv.reset() - -# Configure the codec interface for I2S mode with 16-bit data -print("Configuring codec interface...") -tlv.set_codec_interface(adafruit_tlv320.FORMAT_I2S, adafruit_tlv320.DATA_LEN_16) - -# Configure PLL with BCLK as input -print("Configuring clocks...") -tlv.pll_clock_input = adafruit_tlv320.PLL_CLKIN_BCLK -tlv.codec_clock_input = adafruit_tlv320.CODEC_CLKIN_PLL - -# For a standard 44.1kHz sample rate with 16-bit stereo, BCLK is 1.4112MHz -# We need to configure PLL to generate the appropriate internal clocks -# These PLL settings are very dependent on your exact I2S configuration -# You may need to experiment with these values -tlv.set_pll_values(1, 1, 7, 6144) # Different PLL values to try -tlv.set_ndac(True, 4) -tlv.set_mdac(True, 1) - -# Power up the PLL -print("Powering up PLL...") -tlv.pll_power = True -time.sleep(0.1) # Give PLL time to stabilize - -# Set up DAC data path - explicitly enable both channels -print("Setting up DAC data path...") -tlv.set_dac_data_path(True, True, - adafruit_tlv320.DAC_PATH_NORMAL, - adafruit_tlv320.DAC_PATH_NORMAL) - -# Configure volume - ensure we're unmuted and at a reasonable level -print("Setting DAC volume...") -tlv.set_dac_volume_control(False, False) # Make sure both channels are unmuted -tlv.set_channel_volume(False, 0) # Left channel at 0dB (max) -tlv.set_channel_volume(True, 0) # Right channel at 0dB (max) - -# Set up analog routing - route DAC to headphone -print("Configuring analog inputs...") -tlv.configure_analog_inputs( - adafruit_tlv320.DAC_ROUTE_HP, # Route left DAC directly to headphone - adafruit_tlv320.DAC_ROUTE_HP # Route right DAC directly to headphone -) - -# Configure headphone driver - ensure it's powered up and unmuted -print("Configuring headphone drivers...") -tlv.configure_headphone_driver(True, True) # Power up both left and right drivers - -# Explicitly set headphone volume to a high level -tlv.set_hpl_volume(True, 0) # Enable route with gain of 20 -tlv.set_hpr_volume(True, 0) # Enable route with gain of 20 - -# Ensure the headphone drivers are unmuted with gain -tlv.configure_hpl_pga(-1, True) # Max gain (9dB), unmuted -tlv.configure_hpr_pga(-1, True) # Max gain (9dB), unmuted - -print("DAC configuration complete!") - -# Initialize I2S for audio playback -# Depending on your board, these pins may be different -# BCLK, WCLK/LRCLK, DATA -audio = audiobusio.I2SOut(board.I2S_BCLK, board.I2S_WS, board.I2S_DIN) - -# Generate a sine wave at a higher volume -tone_volume = 0.1 # Increased volume -frequency = 440 # 440 Hz tone (A4) -sample_rate = 8000 # Sample rate in Hz -length = sample_rate // frequency -sine_wave = array.array("h", [0] * length) -for i in range(length): - sine_wave[i] = int((math.sin(math.pi * 2 * i / length)) * tone_volume * (2 ** 15 - 1)) - -sine_wave_sample = audiocore.RawSample(sine_wave, sample_rate=sample_rate) - -print("Starting audio playback...") - -# Play the tone without stopping -audio.play(sine_wave_sample, loop=True) - -# Keep the program running -while True: - time.sleep(1) - print("Tone is playing... Ctrl+C to stop") \ No newline at end of file From f5db1f6348450900a1bd7180c198cf0bd1cd3d34 Mon Sep 17 00:00:00 2001 From: BlitzCityDIY Date: Wed, 19 Mar 2025 11:27:57 -0400 Subject: [PATCH 05/11] remove todos, tested with fruitjam rev b --- README.rst | 38 ++++++++++------ adafruit_tlv320.py | 86 ++++++++++++++++++++++------------- docs/conf.py | 1 - docs/examples.rst | 9 ++++ docs/index.rst | 6 --- examples/tlv320_fulltest.py | 15 ++---- examples/tlv320_simpletest.py | 2 +- requirements.txt | 1 - 8 files changed, 93 insertions(+), 65 deletions(-) diff --git a/README.rst b/README.rst index c4fb62d..4b08231 100644 --- a/README.rst +++ b/README.rst @@ -30,7 +30,6 @@ This driver depends on: * `Adafruit CircuitPython `_ * `Bus Device `_ -* `Register `_ Please ensure all dependencies are available on the CircuitPython filesystem. This is easily achieved by downloading @@ -38,19 +37,8 @@ This is easily achieved by downloading or individual libraries can be installed using `circup `_. - - -.. todo:: Describe the Adafruit product this library works with. For PCBs, you can also add the -image from the assets folder in the PCB's GitHub repo. - -`Purchase one from the Adafruit shop `_ - Installing from PyPI ===================== -.. note:: This library is not available on PyPI yet. Install documentation is included - as a standard element. Stay tuned for PyPI availability! - -.. todo:: Remove the above note if PyPI version is/will be available at time of release. On supported GNU/Linux systems like the Raspberry Pi, you can install the driver locally `from PyPI `_. @@ -101,8 +89,30 @@ Or the following command to update an existing version: Usage Example ============= -.. todo:: Add a quick, simple example. It and other examples should live in the -examples folder and be included in docs/examples.rst. +.. code-block:: python + + import audiobusio + import audiocore + import board + + import adafruit_tlv320 + + i2c = board.I2C() + dac = adafruit_tlv320.TLV320DAC3100(i2c) + + # set mclk, sample rate & bit depth + dac.configure_clocks(mclk_freq=12000000, sample_rate=44100, bit_depth=16) + + # use headphones + # helper function for default settings + dac.headphone_output = True + dac.headphone_volume = -20 # dB + # or use speaker + # helper function for default settings + # dac.speaker_output = True + # dac.speaker_volume = -15 # dB + + audio = audiobusio.I2SOut(board.I2S_BCLK, board.I2S_WS, board.I2S_DIN) Documentation ============= diff --git a/adafruit_tlv320.py b/adafruit_tlv320.py index 305bb6c..2be7ab5 100644 --- a/adafruit_tlv320.py +++ b/adafruit_tlv320.py @@ -517,12 +517,13 @@ class Page0Registers(PagedRegisterBase): self._write_register(_CODEC_IF_CTRL1, value) - def _configure_clocks_for_sample_rate(self, mclk_freq, sample_rate, bit_depth): + def _configure_clocks_for_sample_rate(self, mclk_freq: int, sample_rate: int, bit_depth: int): """Configure clock settings for the specified sample rate. - :param mclk_freq: The main clock frequency in Hz + :param mclk_freq: The main clock frequency in Hz, or 0 to use BCLK as PLL input :param sample_rate: The desired sample rate in Hz :param bit_depth: The bit depth (16, 20, 24, or 32) + :return: True if successful, False otherwise """ if bit_depth == 16: data_len = DATA_LEN_16 @@ -532,7 +533,26 @@ class Page0Registers(PagedRegisterBase): data_len = DATA_LEN_24 else: data_len = DATA_LEN_32 - if mclk_freq % (128 * sample_rate) == 0: + if mclk_freq == 0: + self._set_codec_interface(FORMAT_I2S, data_len) + self._set_bits(_CLOCK_MUX1, 0x03, 2, 0b01) + self._set_bits(_CLOCK_MUX1, 0x03, 0, 0b11) + p, r, j, d = 1, 2, 32, 0 + ndac = 8 + mdac = 2 + dosr = 128 + pr_value = ((p & 0x07) << 4) | (r & 0x0F) + self._write_register(_PLL_PROG_PR, pr_value & 0x7F) + self._write_register(_PLL_PROG_J, j & 0x3F) + self._write_register(_PLL_PROG_D_MSB, (d >> 8) & 0xFF) + self._write_register(_PLL_PROG_D_LSB, d & 0xFF) + self._write_register(_NDAC, 0x80 | (ndac & 0x7F)) + self._write_register(_MDAC, 0x80 | (mdac & 0x7F)) + self._write_register(_DOSR_MSB, (dosr >> 8) & 0xFF) + self._write_register(_DOSR_LSB, dosr & 0xFF) + self._set_bits(_PLL_PROG_PR, 0x01, 7, 1) + time.sleep(0.01) + elif mclk_freq % (128 * sample_rate) == 0: div_ratio = mclk_freq // (128 * sample_rate) self._set_bits(_CLOCK_MUX1, 0x03, 0, 0b00) self._set_bits(_PLL_PROG_PR, 0x01, 7, 0) @@ -542,8 +562,7 @@ class Page0Registers(PagedRegisterBase): self._write_register(_DOSR_MSB, 0) self._write_register(_DOSR_LSB, 128) self._set_codec_interface(FORMAT_I2S, data_len) - - if mclk_freq == 12000000: + elif mclk_freq == 12000000: if sample_rate == 22050: p, r, j, d = 1, 1, 7, 6144 ndac = 8 @@ -565,7 +584,7 @@ class Page0Registers(PagedRegisterBase): mdac = 1 dosr = 128 else: - return False + raise ValueError("Need a valid sample rate: 22050, 44100, 48000 or 96000") elif mclk_freq == 24000000: if sample_rate == 44100: p, r, j, d = 1, 2, 7, 6144 @@ -585,23 +604,22 @@ class Page0Registers(PagedRegisterBase): else: raise ValueError("Need a valid sample rate: 44100, 48000 or 96000") else: - raise ValueError("Need a valid sample rate: 22050, 44100, 48000 or 96000") - - self._set_bits(_CLOCK_MUX1, 0x03, 2, 0b00) - self._set_bits(_CLOCK_MUX1, 0x03, 0, 0b11) - pr_value = ((p & 0x07) << 4) | (r & 0x0F) - self._write_register(_PLL_PROG_PR, pr_value & 0x7F) - self._write_register(_PLL_PROG_J, j & 0x3F) - self._write_register(_PLL_PROG_D_MSB, (d >> 8) & 0xFF) - self._write_register(_PLL_PROG_D_LSB, d & 0xFF) - self._write_register(_NDAC, 0x80 | (ndac & 0x7F)) - self._write_register(_MDAC, 0x80 | (mdac & 0x7F)) - self._write_register(_DOSR_MSB, (dosr >> 8) & 0xFF) - self._write_register(_DOSR_LSB, dosr & 0xFF) - self._set_codec_interface(FORMAT_I2S, data_len) - self._set_bits(_PLL_PROG_PR, 0x01, 7, 1) - time.sleep(0.01) - + raise ValueError("Need a valid MCLK frequency: 12MHz or 24MHz") + if mclk_freq != 0: + self._set_bits(_CLOCK_MUX1, 0x03, 2, 0b00) + self._set_bits(_CLOCK_MUX1, 0x03, 0, 0b11) + pr_value = ((p & 0x07) << 4) | (r & 0x0F) + self._write_register(_PLL_PROG_PR, pr_value & 0x7F) + self._write_register(_PLL_PROG_J, j & 0x3F) + self._write_register(_PLL_PROG_D_MSB, (d >> 8) & 0xFF) + self._write_register(_PLL_PROG_D_LSB, d & 0xFF) + self._write_register(_NDAC, 0x80 | (ndac & 0x7F)) + self._write_register(_MDAC, 0x80 | (mdac & 0x7F)) + self._write_register(_DOSR_MSB, (dosr >> 8) & 0xFF) + self._write_register(_DOSR_LSB, dosr & 0xFF) + self._set_codec_interface(FORMAT_I2S, data_len) + self._set_bits(_PLL_PROG_PR, 0x01, 7, 1) + time.sleep(0.01) class Page1Registers(PagedRegisterBase): """Page 1 registers containing analog output settings, HP/SPK controls, etc.""" @@ -839,7 +857,7 @@ class TLV320DAC3100: # Initialize configuration tracking variables self._sample_rate: int = 44100 # Default self._bit_depth: int = 16 # Default - self._mclk_freq: int = 12000000 # Default + self._mclk_freq: int = 0 # Default blck # Reset the device if not self.reset(): @@ -1709,22 +1727,26 @@ class TLV320DAC3100: headset_detect, button_press, dac_drc, agc_noise, over_current, multiple_pulse ) - def configure_clocks(self, mclk_freq: int, sample_rate: int, bit_depth: int = 16) -> bool: + def configure_clocks(self, sample_rate: int, bit_depth: int = 16, mclk_freq: Optional[int] = None): """Configure the TLV320DAC3100 clock settings. - This function configures all necessary clock settings including MCLK, dividers, - and interface settings to achieve the requested sample rate with the provided - main clock frequency. + This function configures all necessary clock settings including PLL, dividers, + and interface settings to achieve the requested sample rate. - :param mclk_freq: The main clock frequency in Hz (e.g., 12000000 for 12MHz) :param sample_rate: The desired sample rate in Hz (e.g., 44100, 48000) - :param bit_depth: The bit depth (16, 20, 24, or 32) + :param bit_depth: The bit depth (16, 20, 24, or 32), defaults to 16 + :param mclk_freq: The main clock frequency in Hz (e.g., 12000000 for 12MHz) + If None (default), BCLK will be used as the PLL input source :return: True if successful, False otherwise """ - self._mclk_freq = mclk_freq self._sample_rate = sample_rate self._bit_depth = bit_depth - return self._page0._configure_clocks_for_sample_rate(mclk_freq, sample_rate, bit_depth) + if mclk_freq is not None: + self._mclk_freq = mclk_freq + else: + self._mclk_freq = 0 # Internally use 0 to indicate BCLK mode + + return self._page0._configure_clocks_for_sample_rate(self._mclk_freq, sample_rate, bit_depth) @property def headphone_output(self) -> bool: diff --git a/docs/conf.py b/docs/conf.py index 07359f1..42b1ab3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -32,7 +32,6 @@ autodoc_preserve_defaults = True intersphinx_mapping = { "python": ("https://docs.python.org/3", None), "BusDevice": ("https://docs.circuitpython.org/projects/busdevice/en/latest/", None), - "Register": ("https://docs.circuitpython.org/projects/register/en/latest/", None), "CircuitPython": ("https://docs.circuitpython.org/en/latest/", None), } diff --git a/docs/examples.rst b/docs/examples.rst index 3115c4b..aac2b00 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -6,3 +6,12 @@ Ensure your device works with this simple test. .. literalinclude:: ../examples/tlv320_simpletest.py :caption: examples/tlv320_simpletest.py :linenos: + +Full test +---------- + +Demos advanced features of the library. + +.. literalinclude:: ../examples/tlv320_fulltest.py + :caption: examples/tlv320_fulltest.py + :linenos: \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index e826ea3..806537f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -24,15 +24,9 @@ Table of Contents .. toctree:: :caption: Tutorials -.. todo:: Add any Learn guide links here. If there are none, then simply delete this todo and leave - the toctree above for use later. - .. toctree:: :caption: Related Products -.. todo:: Add any product links here. If there are none, then simply delete this todo and leave - the toctree above for use later. - .. toctree:: :caption: Other Links diff --git a/examples/tlv320_fulltest.py b/examples/tlv320_fulltest.py index 65f2c70..df57ca8 100644 --- a/examples/tlv320_fulltest.py +++ b/examples/tlv320_fulltest.py @@ -5,6 +5,9 @@ """ Full TLV320DAC3100 Test Demo all features in the library +Shows advanced control for DAC, headphone and speaker +beyond basic headphone_output & speaker_output helpers +in simpletest. """ import time @@ -51,19 +54,16 @@ time.sleep(0.1) # Give device time to reset print("\n=== Basic Information ===") print(f"Sample rate: {dac.sample_rate} Hz") print(f"Bit depth: {dac.bit_depth}-bit") -print(f"MCLK frequency: {dac.mclk_freq} Hz") print(f"Overtemperature condition: {dac.overtemperature}") # I2S Config -dac.configure_clocks(mclk_freq=12000000, sample_rate=22050, bit_depth=16) +dac.configure_clocks(sample_rate=22050, bit_depth=16) print(f"Sample rate: {dac.sample_rate} Hz") print(f"Bit depth: {dac.bit_depth}-bit") -print(f"MCLK frequency: {dac.mclk_freq} Hz") time.sleep(0.2) -dac.configure_clocks(mclk_freq=12000000, sample_rate=48000, bit_depth=32) +dac.configure_clocks(sample_rate=48000, bit_depth=32) print(f"Sample rate: {dac.sample_rate} Hz") print(f"Bit depth: {dac.bit_depth}-bit") -print(f"MCLK frequency: {dac.mclk_freq} Hz") time.sleep(0.2) # Headphone Output Setup @@ -241,11 +241,6 @@ print(f"New GPIO1 mode: {dac.gpio1_mode}") dac.gpio1_mode = GPIO1_DISABLED # Disable GPIO1 print(f"Disabled GPIO1 mode: {dac.gpio1_mode}") -# Headset Detection -print("\n=== Headset Detection ===") -print(f"Current headset status: {dac.headset_status}") -# 0 = none, 1 = without mic, 3 = with mic - # Volume Control ADC print("\n=== Volume Control ADC ===") print(f"Volume ADC reading: {dac.vol_adc_db} dB") diff --git a/examples/tlv320_simpletest.py b/examples/tlv320_simpletest.py index 9754462..d4a6732 100644 --- a/examples/tlv320_simpletest.py +++ b/examples/tlv320_simpletest.py @@ -17,7 +17,7 @@ i2c = busio.I2C(board.SCL, board.SDA) dac = adafruit_tlv320.TLV320DAC3100(i2c) # set mclk, sample rate & bit depth -dac.configure_clocks(mclk_freq=12000000, sample_rate=44100, bit_depth=16) +dac.configure_clocks(sample_rate=44100, bit_depth=16) # use headphones dac.headphone_output = True diff --git a/requirements.txt b/requirements.txt index 7284723..c1ad2a0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,3 @@ Adafruit-Blinka adafruit-circuitpython-busdevice -adafruit-circuitpython-register From 15d5317a3d4b20d0a7f0c5692949b8794159cf31 Mon Sep 17 00:00:00 2001 From: BlitzCityDIY Date: Wed, 19 Mar 2025 11:40:56 -0400 Subject: [PATCH 06/11] ruff --- adafruit_tlv320.py | 11 ++++++++--- docs/examples.rst | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/adafruit_tlv320.py b/adafruit_tlv320.py index 2be7ab5..52a19dc 100644 --- a/adafruit_tlv320.py +++ b/adafruit_tlv320.py @@ -621,6 +621,7 @@ class Page0Registers(PagedRegisterBase): self._set_bits(_PLL_PROG_PR, 0x01, 7, 1) time.sleep(0.01) + class Page1Registers(PagedRegisterBase): """Page 1 registers containing analog output settings, HP/SPK controls, etc.""" @@ -1727,7 +1728,9 @@ class TLV320DAC3100: headset_detect, button_press, dac_drc, agc_noise, over_current, multiple_pulse ) - def configure_clocks(self, sample_rate: int, bit_depth: int = 16, mclk_freq: Optional[int] = None): + def configure_clocks( + self, sample_rate: int, bit_depth: int = 16, mclk_freq: Optional[int] = None + ): """Configure the TLV320DAC3100 clock settings. This function configures all necessary clock settings including PLL, dividers, @@ -1745,8 +1748,10 @@ class TLV320DAC3100: self._mclk_freq = mclk_freq else: self._mclk_freq = 0 # Internally use 0 to indicate BCLK mode - - return self._page0._configure_clocks_for_sample_rate(self._mclk_freq, sample_rate, bit_depth) + + return self._page0._configure_clocks_for_sample_rate( + self._mclk_freq, sample_rate, bit_depth + ) @property def headphone_output(self) -> bool: diff --git a/docs/examples.rst b/docs/examples.rst index aac2b00..fd84618 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -14,4 +14,4 @@ Demos advanced features of the library. .. literalinclude:: ../examples/tlv320_fulltest.py :caption: examples/tlv320_fulltest.py - :linenos: \ No newline at end of file + :linenos: From 67cc08310d284ff53b8e277a45faedb9c77e216b Mon Sep 17 00:00:00 2001 From: BlitzCityDIY Date: Wed, 19 Mar 2025 11:49:08 -0400 Subject: [PATCH 07/11] docs --- adafruit_tlv320.py | 4 ++-- docs/conf.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/adafruit_tlv320.py b/adafruit_tlv320.py index 52a19dc..35365fe 100644 --- a/adafruit_tlv320.py +++ b/adafruit_tlv320.py @@ -1376,7 +1376,7 @@ class TLV320DAC3100: self._page1._configure_spk_pga(gain, not mute) @property - def dac_flags(self) -> DACFlags: + def dac_flags(self) -> Dict[str, Any]: """Get the DAC and output driver status flags. :return: Dictionary with status flags @@ -1425,7 +1425,7 @@ class TLV320DAC3100: return self._page0._get_din_input() @property - def codec_interface(self) -> CodecInterface: + def codec_interface(self) -> Dict[str, Any]: """Get the current codec interface settings. :return: Dictionary with codec interface settings diff --git a/docs/conf.py b/docs/conf.py index 42b1ab3..6ec6938 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -25,7 +25,7 @@ extensions = [ # Uncomment the below if you use native CircuitPython modules such as # digitalio, micropython and busio. List the modules you use. Without it, the # autodoc module docs will fail to generate with a warning. -# autodoc_mock_imports = ["digitalio", "busio"] +autodoc_mock_imports = ["digitalio", "busio", "adafruit_bus_device", "micropython"] autodoc_preserve_defaults = True From 2a2e91727ff43e580cc9080db21fc6f91b189509 Mon Sep 17 00:00:00 2001 From: Liz Date: Wed, 19 Mar 2025 13:42:42 -0400 Subject: [PATCH 08/11] fix property comments --- adafruit_tlv320.py | 214 ++++++++++++++++++++++----------------------- 1 file changed, 103 insertions(+), 111 deletions(-) diff --git a/adafruit_tlv320.py b/adafruit_tlv320.py index 35365fe..492671c 100644 --- a/adafruit_tlv320.py +++ b/adafruit_tlv320.py @@ -190,7 +190,7 @@ class PagedRegisterBase: i2c.write(self._buffer) def _read_register(self, register): - """Read a value from a register. + """Value from a register. :param register: The register address :return: The register value @@ -203,14 +203,14 @@ class PagedRegisterBase: return self._buffer[0] def _set_page(self): - """Set the current register page.""" + """The current register page.""" self._buffer[0] = _PAGE_SELECT self._buffer[1] = self._page with self._device as i2c: i2c.write(self._buffer) def _get_bits(self, register, mask, shift): - """Read specific bits from a register. + """Specific bits from a register. :param register: The register address :param mask: The bit mask (after shifting) @@ -221,7 +221,7 @@ class PagedRegisterBase: return (value >> shift) & mask def _set_bits(self, register, mask, shift, value): - """Set specific bits in a register. + """Specific bits in a register. :param register: The register address :param mask: The bit mask (after shifting) @@ -280,7 +280,7 @@ class Page0Registers(PagedRegisterBase): self._write_register(_INT1_CTRL, value) def _set_gpio1_mode(self, mode): - """Set the GPIO1 pin mode.""" + """The GPIO1 pin mode.""" return self._set_bits(_GPIO1_CTRL, 0x0F, 2, mode) def _set_headset_detect(self, enable, detect_debounce=0, button_debounce=0): @@ -320,7 +320,7 @@ class Page0Registers(PagedRegisterBase): self._write_register(_DAC_VOL_CTRL, value) def _set_channel_volume(self, right_channel, db): - """Set DAC channel volume in dB.""" + """DAC channel volume in dB.""" if db > 24.0: db = 24.0 if db < -63.5: @@ -335,7 +335,7 @@ class Page0Registers(PagedRegisterBase): self._write_register(_DAC_LVOL, reg_val & 0xFF) def _get_dac_flags(self): - """Get the DAC and output driver status flags. + """The DAC and output driver status flags. :return: Dictionary with status flags for various components """ @@ -362,21 +362,21 @@ class Page0Registers(PagedRegisterBase): } def get_gpio1_input(self): - """Get the current GPIO1 input value. + """The current GPIO1 input value. :return: Current GPIO1 input state (True/False) """ return bool(self._get_bits(_GPIO1_CTRL, 0x01, 1)) def _get_din_input(self): - """Get the current DIN input value. + """The current DIN input value. :return: Current DIN input state (True/False) """ return bool(self._get_bits(_DIN_CTRL, 0x01, 0)) def _get_codec_interface(self): - """Get the current codec interface settings. + """The current codec interface settings. :return: Dictionary with format, data_len, bclk_out, and wclk_out values """ @@ -394,7 +394,7 @@ class Page0Registers(PagedRegisterBase): } def _get_dac_data_path(self): - """Get the current DAC data path configuration. + """The current DAC data path configuration. :return: Dictionary with DAC data path settings """ @@ -414,7 +414,7 @@ class Page0Registers(PagedRegisterBase): } def _get_dac_volume_control(self): - """Get the current DAC volume control configuration. + """The current DAC volume control configuration. :return: Dictionary with volume control settings """ @@ -426,7 +426,7 @@ class Page0Registers(PagedRegisterBase): return {"left_mute": left_mute, "right_mute": right_mute, "control": control} def _get_channel_volume(self, right_channel): - """Get DAC channel volume in dB. + """DAC channel volume in dB. :param right_channel: True for right channel, False for left channel :return: Current volume in dB @@ -440,7 +440,7 @@ class Page0Registers(PagedRegisterBase): return steps * 0.5 def _get_headset_status(self): - """Get current headset detection status. + """Current headset detection status. :return: Integer value representing headset status (0=none, 1=without mic, 3=with mic) """ @@ -448,7 +448,7 @@ class Page0Registers(PagedRegisterBase): return status_bits def _config_vol_adc(self, pin_control=False, use_mclk=False, hysteresis=0, rate=0): - """Configure the Volume/MicDet pin ADC. + """The Volume/MicDet pin ADC. :param pin_control: Enable pin control of DAC volume :param use_mclk: Use MCLK instead of internal RC oscillator @@ -462,7 +462,7 @@ class Page0Registers(PagedRegisterBase): self._write_register(_VOL_ADC_CTRL, value) def _read_vol_adc_db(self): - """Read the current volume from the Volume ADC in dB. + """The current volume from the Volume ADC in dB. :return: Current volume in dB (+18 to -63 dB) """ @@ -509,7 +509,7 @@ class Page0Registers(PagedRegisterBase): self._write_register(_INT2_CTRL, value) def _set_codec_interface(self, format, data_len, bclk_out=False, wclk_out=False): - """Set the codec interface parameters.""" + """The codec interface parameters.""" value = (format & 0x03) << 6 value |= (data_len & 0x03) << 4 value |= (1 if bclk_out else 0) << 3 @@ -518,7 +518,7 @@ class Page0Registers(PagedRegisterBase): self._write_register(_CODEC_IF_CTRL1, value) def _configure_clocks_for_sample_rate(self, mclk_freq: int, sample_rate: int, bit_depth: int): - """Configure clock settings for the specified sample rate. + """Clock settings for the specified sample rate. :param mclk_freq: The main clock frequency in Hz, or 0 to use BCLK as PLL input :param sample_rate: The desired sample rate in Hz @@ -643,7 +643,7 @@ class Page1Registers(PagedRegisterBase): def _configure_headphone_driver( self, left_powered, right_powered, common=HP_COMMON_1_35V, power_down_on_scd=False ): - """Configure headphone driver settings.""" + """Headphone driver settings.""" value = 0x04 # bit 2 must be 1 if left_powered: value |= 1 << 7 @@ -664,7 +664,7 @@ class Page1Registers(PagedRegisterBase): right_ain2=False, hpl_routed_to_hpr=False, ): - """Configure DAC and analog input routing.""" + """DAC and analog input routing.""" value = 0 value |= (left_dac & 0x03) << 6 if left_ain1: @@ -680,28 +680,28 @@ class Page1Registers(PagedRegisterBase): self._write_register(_OUT_ROUTING, value) def _set_hpl_volume(self, route_enabled, gain=0x7F): - """Set HPL analog volume control.""" + """HPL analog volume control.""" if gain > 0x7F: gain = 0x7F value = ((1 if route_enabled else 0) << 7) | (gain & 0x7F) self._write_register(_HPL_VOL, value) def _set_hpr_volume(self, route_enabled, gain=0x7F): - """Set HPR analog volume control.""" + """HPR analog volume control.""" if gain > 0x7F: gain = 0x7F value = ((1 if route_enabled else 0) << 7) | (gain & 0x7F) self._write_register(_HPR_VOL, value) def _set_spk_volume(self, route_enabled, gain=0x7F): - """Set Speaker analog volume control.""" + """Speaker analog volume control.""" if gain > 0x7F: gain = 0x7F value = ((1 if route_enabled else 0) << 7) | (gain & 0x7F) self._write_register(_SPK_VOL, value) def _configure_hpl_pga(self, gain_db=0, unmute=True): - """Configure HPL driver PGA settings.""" + """HPL driver PGA settings.""" if gain_db > 9: raise ValueError("Gain cannot be greater than 9") value = (gain_db & 0x0F) << 3 @@ -710,7 +710,7 @@ class Page1Registers(PagedRegisterBase): self._write_register(_HPL_DRIVER, value) def _configure_hpr_pga(self, gain_db=0, unmute=True): - """Configure HPR driver PGA settings.""" + """HPR driver PGA settings.""" if gain_db > 9: raise ValueError("Gain cannot be greater than 9") value = (gain_db & 0x0F) << 3 @@ -719,7 +719,7 @@ class Page1Registers(PagedRegisterBase): self._write_register(_HPR_DRIVER, value) def _configure_spk_pga(self, gain=SPK_GAIN_6DB, unmute=True): - """Configure Speaker driver settings.""" + """Speaker driver settings.""" value = (gain & 0x03) << 3 if unmute: value |= 1 << 2 @@ -784,7 +784,7 @@ class Page1Registers(PagedRegisterBase): self._write_register(_HP_POP, value) def _set_speaker_wait_time(self, wait_time=0): - """Set speaker power-up wait time. + """Speaker power-up wait time. :param wait_time: Speaker power-up wait duration (0-7) :return: True if successful @@ -813,7 +813,7 @@ class Page1Registers(PagedRegisterBase): self._write_register(_MICBIAS, value) # Using constant instead of 0x2E def _set_input_common_mode(self, ain1_cm, ain2_cm): - """Set analog input common mode connections.""" + """Analog input common mode connections.""" value = 0 if ain1_cm: value |= 1 << 7 @@ -826,7 +826,7 @@ class Page3Registers(PagedRegisterBase): """Page 3 registers containing timer settings.""" def __init__(self, i2c_device): - """Initialize Page 3 registers. + """Page 3 registers. :param i2c_device: The I2C device """ @@ -884,7 +884,7 @@ class TLV320DAC3100: def set_headset_detect( self, enable: bool, detect_debounce: int = 0, button_debounce: int = 0 ) -> bool: - """Configure headset detection settings. + """Headset detection settings. :param enable: Boolean to enable/disable headset detection :param detect_debounce: One of the DEBOUNCE_* constants for headset detect @@ -930,7 +930,7 @@ class TLV320DAC3100: over_current: bool, multiple_pulse: bool, ) -> bool: - """Configure the INT1 interrupt sources. + """The INT1 interrupt sources. :param headset_detect: Enable headset detection interrupt :param button_press: Enable button press detection interrupt @@ -946,7 +946,7 @@ class TLV320DAC3100: @property def left_dac(self) -> bool: - """Get the left DAC enabled status. + """The left DAC enabled status. :return: True if left DAC is enabled, False otherwise """ @@ -954,7 +954,7 @@ class TLV320DAC3100: @left_dac.setter def left_dac(self, enabled: bool) -> None: - """Set the left DAC enabled status. + """The left DAC enabled status. :param enabled: True to enable left DAC, False to disable """ @@ -969,7 +969,7 @@ class TLV320DAC3100: @property def right_dac(self) -> bool: - """Get the right DAC enabled status. + """The right DAC enabled status. :return: True if right DAC is enabled, False otherwise """ @@ -977,7 +977,7 @@ class TLV320DAC3100: @right_dac.setter def right_dac(self, enabled: bool) -> None: - """Set the right DAC enabled status. + """The right DAC enabled status. :param enabled: True to enable right DAC, False to disable """ @@ -992,7 +992,7 @@ class TLV320DAC3100: @property def left_dac_path(self) -> int: - """Get the left DAC path setting. + """The left DAC path setting. :return: One of the DAC_PATH_* constants """ @@ -1000,7 +1000,7 @@ class TLV320DAC3100: @left_dac_path.setter def left_dac_path(self, path: int) -> None: - """Set the left DAC path. + """The left DAC path. :param path: One of the DAC_PATH_* constants :raises ValueError: If path is not a valid DAC_PATH_* constant @@ -1023,7 +1023,7 @@ class TLV320DAC3100: @property def right_dac_path(self) -> int: - """Get the right DAC path setting. + """The right DAC path setting. :return: One of the DAC_PATH_* constants """ @@ -1031,7 +1031,7 @@ class TLV320DAC3100: @right_dac_path.setter def right_dac_path(self, path: int) -> None: - """Set the right DAC path. + """The right DAC path. :param path: One of the DAC_PATH_* constants :raises ValueError: If path is not a valid DAC_PATH_* constant @@ -1054,7 +1054,7 @@ class TLV320DAC3100: @property def dac_volume_step(self) -> int: - """Get the DAC volume step setting. + """The DAC volume step setting. :return: One of the VOLUME_STEP_* constants """ @@ -1062,7 +1062,7 @@ class TLV320DAC3100: @dac_volume_step.setter def dac_volume_step(self, step: int) -> None: - """Set the DAC volume step setting. + """The DAC volume step setting. :param step: One of the VOLUME_STEP_* constants :raises ValueError: If step is not a valid VOLUME_STEP_* constant @@ -1092,7 +1092,7 @@ class TLV320DAC3100: right_ain2: bool = False, hpl_routed_to_hpr: bool = False, ) -> bool: - """Configure DAC and analog input routing. + """DAC and analog input routing. :param left_dac: One of the DAC_ROUTE_* constants for left DAC routing :param right_dac: One of the DAC_ROUTE_* constants for right DAC routing @@ -1121,7 +1121,7 @@ class TLV320DAC3100: @property def left_dac_mute(self) -> bool: - """Get the left DAC mute status. + """The left DAC mute status. :return: True if left DAC is muted, False otherwise """ @@ -1129,7 +1129,7 @@ class TLV320DAC3100: @left_dac_mute.setter def left_dac_mute(self, mute: bool) -> None: - """Set the left DAC mute status. + """The left DAC mute status. :param mute: True to mute left DAC, False to unmute """ @@ -1138,7 +1138,7 @@ class TLV320DAC3100: @property def right_dac_mute(self) -> bool: - """Get the right DAC mute status. + """The right DAC mute status. :return: True if right DAC is muted, False otherwise """ @@ -1146,7 +1146,7 @@ class TLV320DAC3100: @right_dac_mute.setter def right_dac_mute(self, mute: bool) -> None: - """Set the right DAC mute status. + """The right DAC mute status. :param mute: True to mute right DAC, False to unmute """ @@ -1155,7 +1155,7 @@ class TLV320DAC3100: @property def dac_volume_control_mode(self) -> int: - """Get the DAC volume control mode. + """The DAC volume control mode. :return: One of the VOL_* constants """ @@ -1163,7 +1163,7 @@ class TLV320DAC3100: @dac_volume_control_mode.setter def dac_volume_control_mode(self, mode: int) -> None: - """Set the volume control mode. + """The volume control mode. :param mode: One of the VOL_* constants for volume control mode :raises ValueError: If mode is not a valid VOL_* constant @@ -1178,7 +1178,7 @@ class TLV320DAC3100: @property def left_dac_channel_volume(self) -> float: - """Get left DAC channel volume in dB. + """Left DAC channel volume in dB. :return: Volume in dB """ @@ -1186,7 +1186,7 @@ class TLV320DAC3100: @left_dac_channel_volume.setter def left_dac_channel_volume(self, db: float) -> None: - """Set left DAC channel volume in dB. + """Left DAC channel volume in dB. :param db: Volume in dB """ @@ -1194,7 +1194,7 @@ class TLV320DAC3100: @property def right_dac_channel_volume(self) -> float: - """Get right DAC channel volume in dB. + """Right DAC channel volume in dB. :return: Volume in dB """ @@ -1202,7 +1202,7 @@ class TLV320DAC3100: @right_dac_channel_volume.setter def right_dac_channel_volume(self, db: float) -> None: - """Set right DAC channel volume in dB. + """Right DAC channel volume in dB. :param db: Volume in dB """ @@ -1215,7 +1215,7 @@ class TLV320DAC3100: common: int = 0, power_down_on_scd: bool = False, ) -> bool: - """Configure headphone driver settings. + """Headphone driver settings. :param left_powered: Boolean to power left headphone driver :param right_powered: Boolean to power right headphone driver @@ -1241,7 +1241,7 @@ class TLV320DAC3100: ) def manual_headphone_left_volume(self, route_enabled: bool, gain: int = 0x7F) -> bool: - """Set HPL analog volume control. + """HPL analog volume control. :param route_enabled: Enable routing to HPL :param gain: Analog volume control value (0-127) @@ -1250,7 +1250,7 @@ class TLV320DAC3100: return self._page1._set_hpl_volume(route_enabled, gain) def manual_headphone_right_volume(self, route_enabled: bool, gain: int = 0x7F) -> bool: - """Set HPR analog volume control. + """HPR analog volume control. :param route_enabled: Enable routing to HPR :param gain: Analog volume control value (0-127) @@ -1260,7 +1260,7 @@ class TLV320DAC3100: @property def headphone_left_gain(self) -> int: - """Get the left headphone gain in dB. + """The left headphone gain in dB. :return: Gain value in dB """ @@ -1269,7 +1269,7 @@ class TLV320DAC3100: @headphone_left_gain.setter def headphone_left_gain(self, gain_db: int) -> None: - """Set the left headphone gain in dB. + """The left headphone gain in dB. :param gain_db: Gain value in dB """ @@ -1278,7 +1278,7 @@ class TLV320DAC3100: @property def headphone_left_mute(self) -> bool: - """Get the left headphone mute status. + """The left headphone mute status. :return: True if left headphone is muted, False otherwise """ @@ -1287,7 +1287,7 @@ class TLV320DAC3100: @headphone_left_mute.setter def headphone_left_mute(self, mute: bool) -> None: - """Set the left headphone mute status. + """The left headphone mute status. :param mute: True to mute left headphone, False to unmute """ @@ -1296,7 +1296,7 @@ class TLV320DAC3100: @property def headphone_right_gain(self) -> int: - """Get the right headphone gain in dB. + """The right headphone gain in dB. :return: Gain value in dB """ @@ -1305,7 +1305,7 @@ class TLV320DAC3100: @headphone_right_gain.setter def headphone_right_gain(self, gain_db: int) -> None: - """Set the right headphone gain in dB. + """The right headphone gain in dB. :param gain_db: Gain value in dB """ @@ -1314,7 +1314,7 @@ class TLV320DAC3100: @property def headphone_right_mute(self) -> bool: - """Get the right headphone mute status. + """The right headphone mute status. :return: True if right headphone is muted, False otherwise """ @@ -1323,7 +1323,7 @@ class TLV320DAC3100: @headphone_right_mute.setter def headphone_right_mute(self, mute: bool) -> None: - """Set the right headphone mute status. + """The right headphone mute status. :param mute: True to mute right headphone, False to unmute """ @@ -1332,7 +1332,7 @@ class TLV320DAC3100: @property def speaker_gain(self) -> int: - """Get the speaker gain setting in dB. + """The speaker gain setting in dB. :return: The gain value in dB """ @@ -1341,7 +1341,7 @@ class TLV320DAC3100: @speaker_gain.setter def speaker_gain(self, gain_db: int) -> None: - """Set the speaker gain in dB. + """The speaker gain in dB. :param gain_db: Speaker gain in dB (6, 12, 18, or 24) :raises ValueError: If gain_db is not a valid value @@ -1358,7 +1358,7 @@ class TLV320DAC3100: @property def speaker_mute(self) -> bool: - """Get the speaker mute status. + """The speaker mute status. :return: True if speaker is muted, False otherwise """ @@ -1367,7 +1367,7 @@ class TLV320DAC3100: @speaker_mute.setter def speaker_mute(self, mute: bool) -> None: - """Set the speaker mute status. + """The speaker mute status. :param mute: True to mute speaker, False to unmute """ @@ -1377,7 +1377,7 @@ class TLV320DAC3100: @property def dac_flags(self) -> Dict[str, Any]: - """Get the DAC and output driver status flags. + """The DAC and output driver status flags. :return: Dictionary with status flags """ @@ -1385,7 +1385,7 @@ class TLV320DAC3100: @property def gpio1_mode(self) -> int: - """Get the current GPIO1 pin mode. + """The current GPIO1 pin mode. :return: One of the GPIO1_* mode constants """ @@ -1394,7 +1394,7 @@ class TLV320DAC3100: @gpio1_mode.setter def gpio1_mode(self, mode: int) -> None: - """Set the GPIO1 pin mode. + """The GPIO1 pin mode. :param mode: One of the GPIO1_* mode constants :raises ValueError: If mode is not a valid GPIO1_* constant @@ -1418,7 +1418,7 @@ class TLV320DAC3100: @property def din_input(self) -> int: - """Get the current DIN input value. + """The current DIN input value. :return: The DIN input value """ @@ -1426,7 +1426,7 @@ class TLV320DAC3100: @property def codec_interface(self) -> Dict[str, Any]: - """Get the current codec interface settings. + """The current codec interface settings. :return: Dictionary with codec interface settings """ @@ -1474,7 +1474,7 @@ class TLV320DAC3100: @property def headset_status(self) -> int: - """Get current headset detection status. + """Current headset detection status. :return: Integer value representing headset status (0=none, 1=without mic, 3=with mic) """ @@ -1482,7 +1482,7 @@ class TLV320DAC3100: @property def reset_speaker_on_scd(self) -> bool: - """Get the speaker reset behavior on short circuit detection. + """The speaker reset behavior on short circuit detection. :return: True if speaker resets on short circuit, False otherwise """ @@ -1491,15 +1491,14 @@ class TLV320DAC3100: @reset_speaker_on_scd.setter def reset_speaker_on_scd(self, reset: bool) -> None: - """Configure speaker reset behavior on short circuit detection. - + """ :param reset: True to reset speaker on short circuit, False to remain unchanged """ self._page1._reset_speaker_on_scd(reset) @property def reset_headphone_on_scd(self) -> bool: - """Get the headphone reset behavior on short circuit detection. + """The headphone reset behavior on short circuit detection. :return: True if headphone resets on short circuit, False otherwise """ @@ -1508,8 +1507,7 @@ class TLV320DAC3100: @reset_headphone_on_scd.setter def reset_headphone_on_scd(self, reset: bool) -> None: - """Configure headphone reset behavior on short circuit detection. - + """ :param reset: True to reset headphone on short circuit, False to remain unchanged """ self._page1._reset_headphone_on_scd(reset) @@ -1517,7 +1515,7 @@ class TLV320DAC3100: def configure_headphone_pop( self, wait_for_powerdown: bool = True, powerup_time: int = 0x07, ramp_time: int = 0x03 ) -> bool: - """Configure headphone pop removal settings. + """Headphone pop removal settings. :param wait_for_powerdown: Wait for amp powerdown before DAC powerdown :param powerup_time: Driver power-on time (0-11) @@ -1528,7 +1526,7 @@ class TLV320DAC3100: @property def speaker_wait_time(self) -> int: - """Get the current speaker power-up wait time. + """The current speaker power-up wait time. :return: The wait time setting (0-7) """ @@ -1537,7 +1535,7 @@ class TLV320DAC3100: @speaker_wait_time.setter def speaker_wait_time(self, wait_time: int) -> None: - """Set speaker power-up wait time. + """Speaker power-up wait time. :param wait_time: Speaker power-up wait duration (0-7) """ @@ -1545,7 +1543,7 @@ class TLV320DAC3100: @property def headphone_lineout(self) -> bool: - """Get the current headphone line-out configuration. + """The current headphone line-out configuration. :return: True if both channels are configured as line-out, False otherwise """ @@ -1556,8 +1554,7 @@ class TLV320DAC3100: @headphone_lineout.setter def headphone_lineout(self, enabled: bool) -> None: - """Configure headphone outputs as line-out. - + """ :param enabled: True to configure both channels as line-out, False otherwise """ self._page1._headphone_lineout(enabled, enabled) @@ -1565,7 +1562,7 @@ class TLV320DAC3100: def config_mic_bias( self, power_down: bool = False, always_on: bool = False, voltage: int = 0 ) -> bool: - """Configure MICBIAS settings. + """MICBIAS settings. :param power_down: Enable software power down :param always_on: Keep MICBIAS on even without headset @@ -1575,7 +1572,7 @@ class TLV320DAC3100: return self._page1._config_mic_bias(power_down, always_on, voltage) def set_input_common_mode(self, ain1_cm: bool, ain2_cm: bool) -> bool: - """Set analog input common mode connections. + """Analog input common mode connections. :param ain1_cm: Connect AIN1 to common mode when unused :param ain2_cm: Connect AIN2 to common mode when unused @@ -1584,7 +1581,7 @@ class TLV320DAC3100: return self._page1._set_input_common_mode(ain1_cm, ain2_cm) def config_delay_divider(self, use_mclk: bool = True, divider: int = 1) -> bool: - """Configure programmable delay timer clock source and divider. + """Programmable delay timer clock source and divider. :param use_mclk: True to use external MCLK, False for internal oscillator :param divider: Clock divider (1-127, or 0 for 128) @@ -1594,7 +1591,7 @@ class TLV320DAC3100: @property def vol_adc_pin_control(self) -> bool: - """Get the volume ADC pin control status. + """The volume ADC pin control status. :return: True if volume ADC pin control is enabled, False otherwise """ @@ -1603,8 +1600,7 @@ class TLV320DAC3100: @vol_adc_pin_control.setter def vol_adc_pin_control(self, enabled: bool) -> None: - """Enable or disable volume ADC pin control. - + """ :param enabled: True to enable volume ADC pin control, False to disable """ current_config = self._get_vol_adc_config() @@ -1617,7 +1613,7 @@ class TLV320DAC3100: @property def vol_adc_use_mclk(self) -> bool: - """Get the volume ADC use MCLK status. + """The volume ADC use MCLK status. :return: True if volume ADC uses MCLK, False otherwise """ @@ -1626,8 +1622,7 @@ class TLV320DAC3100: @vol_adc_use_mclk.setter def vol_adc_use_mclk(self, use_mclk: bool) -> None: - """Set whether to use MCLK for volume ADC. - + """ :param use_mclk: True to use MCLK, False to use internal oscillator """ current_config = self._get_vol_adc_config() @@ -1640,7 +1635,7 @@ class TLV320DAC3100: @property def vol_adc_hysteresis(self) -> int: - """Get the volume ADC hysteresis setting. + """The volume ADC hysteresis setting. :return: Hysteresis value (0-3) """ @@ -1649,8 +1644,7 @@ class TLV320DAC3100: @vol_adc_hysteresis.setter def vol_adc_hysteresis(self, hysteresis: int) -> None: - """Set the volume ADC hysteresis. - + """ :param hysteresis: Hysteresis value (0-3) """ current_config = self._get_vol_adc_config() @@ -1663,7 +1657,7 @@ class TLV320DAC3100: @property def vol_adc_rate(self) -> int: - """Get the volume ADC sampling rate. + """The volume ADC sampling rate. :return: Rate value (0-7) """ @@ -1672,7 +1666,7 @@ class TLV320DAC3100: @vol_adc_rate.setter def vol_adc_rate(self, rate: int) -> None: - """Set the volume ADC sampling rate. + """ :param rate: Rate value (0-7) """ @@ -1685,7 +1679,7 @@ class TLV320DAC3100: ) def _get_vol_adc_config(self) -> Dict[str, Any]: - """Helper method to get the current volume ADC configuration. + """Helper method for the current volume ADC configuration. :return: Dictionary with current volume ADC configuration """ @@ -1699,7 +1693,7 @@ class TLV320DAC3100: @property def vol_adc_db(self) -> float: - """Read the current volume from the Volume ADC in dB. + """The current volume from the Volume ADC in dB. :return: Volume in dB """ @@ -1756,7 +1750,7 @@ class TLV320DAC3100: @property def headphone_output(self) -> bool: """Headphone output helper with quickstart settings for users. - Get headphone output state (True if either left or right channel is powered). + Headphone output state (True if either left or right channel is powered). :return: True if headphone output is enabled, False otherwise """ @@ -1767,8 +1761,7 @@ class TLV320DAC3100: @headphone_output.setter def headphone_output(self, enabled: bool) -> None: - """Headphone output helper with quickstart settings for users. - Enable or disable headphone outputs. + """ :param enabled: True to enable headphone output, False to disable """ @@ -1798,7 +1791,7 @@ class TLV320DAC3100: @property def speaker_output(self) -> bool: """Speaker output helper with quickstart settings for users. - Get speaker output state. + Speaker output state. :return: True if speaker output is enabled, False otherwise """ @@ -1806,8 +1799,7 @@ class TLV320DAC3100: @speaker_output.setter def speaker_output(self, enabled: bool) -> None: - """Speaker output helper with quickstart settings for users. - Enable or disable speaker output. + """ :param enabled: True to enable speaker, False to disable """ @@ -1826,7 +1818,7 @@ class TLV320DAC3100: @property def headphone_volume(self) -> float: - """Get the current headphone volume in dB. + """The current headphone volume in dB. :return: The volume in dB (0 = max, -63.5 = min) """ @@ -1840,7 +1832,7 @@ class TLV320DAC3100: @headphone_volume.setter def headphone_volume(self, db: float) -> None: - """Set headphone volume in dB. + """ :param db: Volume in dB (0 = max, -63.5 = min) """ @@ -1855,7 +1847,7 @@ class TLV320DAC3100: @property def speaker_volume(self) -> float: - """Get the current speaker volume in dB. + """The current speaker volume in dB. :return: The volume in dB (0 = max, -63.5 = min) """ @@ -1867,7 +1859,7 @@ class TLV320DAC3100: @speaker_volume.setter def speaker_volume(self, db: float) -> None: - """Set speaker volume in dB. + """ :param db: Volume in dB (0 = max, -63.5 = min) """ From 08a9a85b1c6e9ef02129ae587c09284d6dc5fa9c Mon Sep 17 00:00:00 2001 From: Liz Date: Fri, 21 Mar 2025 17:04:07 -0400 Subject: [PATCH 09/11] fix init order for speaker tested with rev b fruit jam --- adafruit_tlv320.py | 116 +++++++++++++++++++--------------- examples/tlv320_simpletest.py | 9 ++- 2 files changed, 70 insertions(+), 55 deletions(-) diff --git a/adafruit_tlv320.py b/adafruit_tlv320.py index 492671c..1978223 100644 --- a/adafruit_tlv320.py +++ b/adafruit_tlv320.py @@ -229,8 +229,8 @@ class PagedRegisterBase: :param value: The value to set """ reg_value = self._read_register(register) - reg_value &= ~(mask << shift) # Clear the bits - reg_value |= (value & mask) << shift # Set the new bits + reg_value &= ~(mask << shift) + reg_value |= (value & mask) << shift self._write_register(register, reg_value) @@ -327,7 +327,7 @@ class Page0Registers(PagedRegisterBase): db = -63.5 reg_val = int(db * 2) if reg_val == 0x80 or reg_val > 0x30: - return False + raise ValueError if right_channel: self._write_register(_DAC_RVOL, reg_val & 0xFF) @@ -381,10 +381,10 @@ class Page0Registers(PagedRegisterBase): :return: Dictionary with format, data_len, bclk_out, and wclk_out values """ reg_value = self._read_register(_CODEC_IF_CTRL1) - format_val = (reg_value >> 6) & 0x03 # bits 7:6 - data_len = (reg_value >> 4) & 0x03 # bits 5:4 - bclk_out = bool(reg_value & (1 << 3)) # bit 3 - wclk_out = bool(reg_value & (1 << 2)) # bit 2 + format_val = (reg_value >> 6) & 0x03 + data_len = (reg_value >> 4) & 0x03 + bclk_out = bool(reg_value & (1 << 3)) + wclk_out = bool(reg_value & (1 << 2)) return { "format": format_val, @@ -399,11 +399,11 @@ class Page0Registers(PagedRegisterBase): :return: Dictionary with DAC data path settings """ reg_value = self._read_register(_DAC_DATAPATH) - left_dac_on = bool(reg_value & (1 << 7)) # bit 7 - right_dac_on = bool(reg_value & (1 << 6)) # bit 6 - left_path = (reg_value >> 4) & 0x03 # bits 5:4 - right_path = (reg_value >> 2) & 0x03 # bits 3:2 - volume_step = reg_value & 0x03 # bits 1:0 + left_dac_on = bool(reg_value & (1 << 7)) + right_dac_on = bool(reg_value & (1 << 6)) + left_path = (reg_value >> 4) & 0x03 + right_path = (reg_value >> 2) & 0x03 + volume_step = reg_value & 0x03 return { "left_dac_on": left_dac_on, @@ -419,10 +419,9 @@ class Page0Registers(PagedRegisterBase): :return: Dictionary with volume control settings """ reg_value = self._read_register(_DAC_VOL_CTRL) - left_mute = bool(reg_value & (1 << 3)) # bit 3 - right_mute = bool(reg_value & (1 << 2)) # bit 2 - control = reg_value & 0x03 # bits 1:0 - + left_mute = bool(reg_value & (1 << 3)) + right_mute = bool(reg_value & (1 << 2)) + control = reg_value & 0x03 return {"left_mute": left_mute, "right_mute": right_mute, "control": control} def _get_channel_volume(self, right_channel): @@ -533,25 +532,31 @@ class Page0Registers(PagedRegisterBase): data_len = DATA_LEN_24 else: data_len = DATA_LEN_32 + if mclk_freq == 0: - self._set_codec_interface(FORMAT_I2S, data_len) self._set_bits(_CLOCK_MUX1, 0x03, 2, 0b01) self._set_bits(_CLOCK_MUX1, 0x03, 0, 0b11) - p, r, j, d = 1, 2, 32, 0 - ndac = 8 - mdac = 2 + p, r, j, d = 1, 3, 20, 0 + ndac = 5 + mdac = 3 dosr = 128 + # Set the data format + self._set_codec_interface(FORMAT_I2S, data_len) + # Configure PLL pr_value = ((p & 0x07) << 4) | (r & 0x0F) self._write_register(_PLL_PROG_PR, pr_value & 0x7F) self._write_register(_PLL_PROG_J, j & 0x3F) self._write_register(_PLL_PROG_D_MSB, (d >> 8) & 0xFF) self._write_register(_PLL_PROG_D_LSB, d & 0xFF) + # Configure dividers self._write_register(_NDAC, 0x80 | (ndac & 0x7F)) self._write_register(_MDAC, 0x80 | (mdac & 0x7F)) self._write_register(_DOSR_MSB, (dosr >> 8) & 0xFF) self._write_register(_DOSR_LSB, dosr & 0xFF) + # Power up PLL self._set_bits(_PLL_PROG_PR, 0x01, 7, 1) time.sleep(0.01) + elif mclk_freq % (128 * sample_rate) == 0: div_ratio = mclk_freq // (128 * sample_rate) self._set_bits(_CLOCK_MUX1, 0x03, 0, 0b00) @@ -562,6 +567,7 @@ class Page0Registers(PagedRegisterBase): self._write_register(_DOSR_MSB, 0) self._write_register(_DOSR_LSB, 128) self._set_codec_interface(FORMAT_I2S, data_len) + elif mclk_freq == 12000000: if sample_rate == 22050: p, r, j, d = 1, 1, 7, 6144 @@ -585,6 +591,7 @@ class Page0Registers(PagedRegisterBase): dosr = 128 else: raise ValueError("Need a valid sample rate: 22050, 44100, 48000 or 96000") + elif mclk_freq == 24000000: if sample_rate == 44100: p, r, j, d = 1, 2, 7, 6144 @@ -604,7 +611,8 @@ class Page0Registers(PagedRegisterBase): else: raise ValueError("Need a valid sample rate: 44100, 48000 or 96000") else: - raise ValueError("Need a valid MCLK frequency: 12MHz or 24MHz") + raise ValueError("Need a valid MCLK frequency: 12MHz, 24MHz or 0 for BCLK") + if mclk_freq != 0: self._set_bits(_CLOCK_MUX1, 0x03, 2, 0b00) self._set_bits(_CLOCK_MUX1, 0x03, 0, 0b11) @@ -644,7 +652,7 @@ class Page1Registers(PagedRegisterBase): self, left_powered, right_powered, common=HP_COMMON_1_35V, power_down_on_scd=False ): """Headphone driver settings.""" - value = 0x04 # bit 2 must be 1 + value = 0x04 if left_powered: value |= 1 << 7 if right_powered: @@ -652,7 +660,6 @@ class Page1Registers(PagedRegisterBase): value |= (common & 0x03) << 3 if power_down_on_scd: value |= 1 << 1 - self._write_register(_HP_DRIVERS, value) def _configure_analog_inputs( @@ -810,7 +817,7 @@ class Page1Registers(PagedRegisterBase): value = (1 if power_down else 0) << 7 value |= (1 if always_on else 0) << 3 value |= voltage & 0x03 - self._write_register(_MICBIAS, value) # Using constant instead of 0x2E + self._write_register(_MICBIAS, value) def _set_input_common_mode(self, ain1_cm, ain2_cm): """Analog input common mode connections.""" @@ -819,7 +826,7 @@ class Page1Registers(PagedRegisterBase): value |= 1 << 7 if ain2_cm: value |= 1 << 6 - self._write_register(_INPUT_CM, value) # Using constant instead of 0x32 + self._write_register(_INPUT_CM, value) class Page3Registers(PagedRegisterBase): @@ -854,15 +861,23 @@ class TLV320DAC3100: self._page0: "Page0Registers" = Page0Registers(self._device) self._page1: "Page1Registers" = Page1Registers(self._device) self._page3: "Page3Registers" = Page3Registers(self._device) - - # Initialize configuration tracking variables - self._sample_rate: int = 44100 # Default - self._bit_depth: int = 16 # Default + self._sample_rate: int = 44100 + self._bit_depth: int = 16 self._mclk_freq: int = 0 # Default blck - - # Reset the device if not self.reset(): raise RuntimeError("Failed to reset TLV320DAC3100") + time.sleep(0.01) + self._page0._set_channel_volume(False, 0) + self._page0._set_channel_volume(True, 0) + + # Both DACs on with normal path by default + self._page0._set_dac_data_path( + left_dac_on=True, + right_dac_on=True, + left_path=DAC_PATH_NORMAL, + right_path=DAC_PATH_NORMAL, + ) + self._page0._set_dac_volume_control(False, False, VOL_INDEPENDENT) # Basic properties and methods @@ -1762,31 +1777,27 @@ class TLV320DAC3100: @headphone_output.setter def headphone_output(self, enabled: bool) -> None: """ - :param enabled: True to enable headphone output, False to disable """ if enabled: + self.left_dac = True + self.right_dac = True + self.left_dac_channel_volume = 0 + self.right_dac_channel_volume = 0 + self.left_dac_mute = False + self.right_dac_mute = False + self.left_dac_path = DAC_PATH_NORMAL + self.right_dac_path = DAC_PATH_NORMAL + self.headphone_left_gain = 0 + self.headphone_right_gain = 0 self._page1._configure_headphone_driver( left_powered=True, right_powered=True, common=HP_COMMON_1_65V ) - self.headphone_left_gain = 0 - self.headphone_right_gain = 0 + self._page1._configure_analog_inputs(left_dac=DAC_ROUTE_HP, right_dac=DAC_ROUTE_HP) self.headphone_left_mute = False self.headphone_right_mute = False - self._page1._configure_analog_inputs(left_dac=DAC_ROUTE_HP, right_dac=DAC_ROUTE_HP) - self.left_dac = True - self.right_dac = True - self.left_dac_path = DAC_PATH_NORMAL - self.right_dac_path = DAC_PATH_NORMAL - self.headphone_volume = 0 - self.dac_volume_control_mode = VOL_INDEPENDENT - self.left_dac_mute = False - self.right_dac_mute = False - self.left_dac_channel_volume = 0 - self.right_dac_channel_volume = 0 else: self._page1._configure_headphone_driver(left_powered=False, right_powered=False) - self._page1._configure_analog_inputs(left_dac=DAC_ROUTE_NONE, right_dac=DAC_ROUTE_NONE) @property def speaker_output(self) -> bool: @@ -1800,18 +1811,23 @@ class TLV320DAC3100: @speaker_output.setter def speaker_output(self, enabled: bool) -> None: """ - :param enabled: True to enable speaker, False to disable """ if enabled: + self.left_dac = True + self.right_dac = True + self.left_dac_channel_volume = 0 + self.right_dac_channel_volume = 0 + self.left_dac_mute = False + self.right_dac_mute = False + self.left_dac_path = DAC_PATH_NORMAL + self.right_dac_path = DAC_PATH_NORMAL + self.speaker_gain = SPK_GAIN_6DB self._page1._set_speaker_enabled(True) self._page1._configure_analog_inputs( left_dac=DAC_ROUTE_MIXER, right_dac=DAC_ROUTE_MIXER ) - self.left_dac = True - self.right_dac = True self.speaker_volume = -10 - self.speaker_gain = SPK_GAIN_6DB self.speaker_mute = False else: self._page1._set_speaker_enabled(False) diff --git a/examples/tlv320_simpletest.py b/examples/tlv320_simpletest.py index d4a6732..b8d9efb 100644 --- a/examples/tlv320_simpletest.py +++ b/examples/tlv320_simpletest.py @@ -9,22 +9,21 @@ import time import audiobusio import audiocore import board -import busio import adafruit_tlv320 -i2c = busio.I2C(board.SCL, board.SDA) +i2c = board.I2C() dac = adafruit_tlv320.TLV320DAC3100(i2c) -# set mclk, sample rate & bit depth +# set sample rate & bit depth, use bclk dac.configure_clocks(sample_rate=44100, bit_depth=16) # use headphones dac.headphone_output = True -dac.headphone_volume = -20 # dB +dac.headphone_volume = -15 # dB # or use speaker # dac.speaker_output = True -# dac.speaker_volume = -15 # dB +# dac.speaker_volume = -10 # dB audio = audiobusio.I2SOut(board.I2S_BCLK, board.I2S_WS, board.I2S_DIN) # generate a sine wave From 41b982c9566e3951fdd0243d8c7fed874c8334e2 Mon Sep 17 00:00:00 2001 From: Liz Date: Mon, 31 Mar 2025 10:47:42 -0400 Subject: [PATCH 10/11] Update tlv320_fulltest.py --- examples/tlv320_fulltest.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/examples/tlv320_fulltest.py b/examples/tlv320_fulltest.py index df57ca8..44c0854 100644 --- a/examples/tlv320_fulltest.py +++ b/examples/tlv320_fulltest.py @@ -45,11 +45,6 @@ print("Initializing I2C and TLV320DAC3100...") i2c = board.I2C() dac = TLV320DAC3100(i2c) -# Reset the device -print("Resetting the DAC...") -dac.reset() -time.sleep(0.1) # Give device time to reset - # Display basic information print("\n=== Basic Information ===") print(f"Sample rate: {dac.sample_rate} Hz") @@ -57,11 +52,7 @@ print(f"Bit depth: {dac.bit_depth}-bit") print(f"Overtemperature condition: {dac.overtemperature}") # I2S Config -dac.configure_clocks(sample_rate=22050, bit_depth=16) -print(f"Sample rate: {dac.sample_rate} Hz") -print(f"Bit depth: {dac.bit_depth}-bit") -time.sleep(0.2) -dac.configure_clocks(sample_rate=48000, bit_depth=32) +dac.configure_clocks(sample_rate=44000, bit_depth=16) print(f"Sample rate: {dac.sample_rate} Hz") print(f"Bit depth: {dac.bit_depth}-bit") time.sleep(0.2) From 759f7c20755c74d1b07f08c11ddc0ac4160d3c23 Mon Sep 17 00:00:00 2001 From: Liz <23021834+BlitzCityDIY@users.noreply.github.com> Date: Mon, 31 Mar 2025 16:06:29 -0400 Subject: [PATCH 11/11] remove link text Co-authored-by: Scott Shawcroft --- adafruit_tlv320.py | 1 - 1 file changed, 1 deletion(-) diff --git a/adafruit_tlv320.py b/adafruit_tlv320.py index 1978223..9de13a6 100644 --- a/adafruit_tlv320.py +++ b/adafruit_tlv320.py @@ -15,7 +15,6 @@ Implementation Notes **Hardware:** -* `Link Text `_ **Software and Dependencies:**