API Reference
adafruit_nau88l21
CircuitPython driver for the Nuvoton NAU88L21 audio codec
Author(s): Tim Cocks
Implementation Notes
Hardware:
The NAU88L21 is a stereo codec: a DAC driving a headphone amplifier, and an ADC fed by a differential microphone front-end PGA.
Registers are 16-bit addresses with 16-bit big-endian data. Every descriptor in this module therefore has to be built with
register_width=2andlsb_first=False;CAUTION: the 3.5mm amplifier can drive sensitive earbuds to levels that could damage your hearing. Teenage Engineering documentation states: TING is designed to connect to RIDDIM or any sound system, not directly to headphones. the maximum output is 2 VRMS. This can be very loud when connected directly to headphones.
Clocking, and why it needs I2S running first
CircuitPythons rp2 I2S generates BCLK and WS, the codec runs from that external
clock (what the datasheet calls slave mode). Its SYSCLK comes from the internal
FLL locked to the incoming bit clock, which means BCLK must already be running
before NAU88L21.configure_clocks is called, otherwise the FLL has nothing to
lock to. The correct order is:
i2s = audiobusio.I2SOut(bit_clock, word_select, data)
i2s.play(some_looping_sample, loop=True) # BCLK starts here
codec.configure_clocks() # now the FLL can lock
The driver does not own the I2SOut/I2SIn object; you construct it and
keep it.
Full duplex
The internal clock mode object has to exist before the external clock mode one has anything to sync to, and the codec’s FLL has to have BCLK before it can lock:
i2s = audiobusio.I2SOut(bit_clock, word_select, data_out)
codec.configure_clocks()
codec.headphone_output = True
codec.configure_microphone_input()
mic = audioi2sin.I2SIn(bit_clock, word_select, data_in, sample_rate=48000,
bit_depth=16, external_clock=True,
left_justified=True)
Input left_justified
I2SIn wants left_justified=True even though the codec is
in Philips mode. The codec drives ADCDAT one bit clock earlier than
CircuitPython’s own internal clock mode transmitter emits it, and the I2SIn
left_justified flag is what takes that bit back.
Effect chains order
Wire effect chains from the output backwards. I2SOut.play() restarts
the internal clock mode state machine, which knocks an external_clock=True
I2SIn off the frame it had locked to; that I2SIn is re-synced only by a
play() call made on the mic, and effects do not propagate that down to
their own source.
So the call that hands over the mic must come last:
i2s.play(effect) # first
effect.play(mic) # last
Software and Dependencies:
Adafruit CircuitPython firmware for the supported boards: 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
- class adafruit_nau88l21.NAU88L21(i2c: I2C, address: int = _DEFAULT_ADDRESS, reset: bool = True)
Driver for the Nuvoton NAU88L21 stereo audio codec.
- Parameters:
i2c – The I2C bus the codec is connected to.
address – The I2C device address. Defaults to
0x1B.reset – Reset the codec during construction. Defaults to True.
The codec must have a running bit clock before
configure_clocksis called; see the module documentation for the ordering.- property adc_biquad: bool
Whether the capture-path biquad filter (BIQ0) is running.
Enabling a filter whose coefficients have never been programmed is silence, not a bypass. reset leaves every coefficient at zero. Set
adc_biquad_coefficientsfirst, or toBIQUAD_PASSTHROUGH.- Getter:
True if the ADC biquad is enabled.
- Setter:
Enable or disable the ADC biquad.
- property adc_biquad_coefficients: Tuple[float, ...]
The capture-path biquad coefficients, as
(b0, b1, b2, a1, a2).The filter is the usual second-order section:
b0 + b1*z**-1 + b2*z**-2 H(z) = -------------------------- 1 + a1*z**-1 + a2*z**-2
which is exactly the sign convention
scipy.signal.butterand friends return, sob, a = butter(...)maps straight onto(b[0], b[1], b[2], a[1], a[2]).Coefficients are stored in a 19-bit S2.16 fixed-point format, so the range is -4.0 to +3.99998 in steps of 1/65536 and values outside it are clipped. Round-tripping through this property therefore returns the quantized coefficients, not the ones that were written.
- Getter:
Return the five coefficients as floats.
- Setter:
Write all five in a single ten-register transaction, leaving the enable bit as it was.
- property adc_drc: bool
Whether the capture-path dynamic range compressor is running.
Setting this to True applies the chip’s default curve by calling
configure_adc_drcwith no arguments; use that method to shape it.- Getter:
True if the ADC DRC is enabled.
- Setter:
Enable (with default settings) or disable the ADC DRC.
- property adc_soft_mute: bool
The ADC soft mute, which ramps both capture channels down together.
- Getter:
True if the ADC is muted.
- Setter:
Mute or unmute the ADC.
- property adc_volume: float
The ADC digital volume in dB, averaged across the two channels.
Same scale as
dac_volume: -66 dB to +24 dB in 0.5 dB steps, 0 dB unity. Setting it writes both channels of R35 at once.Note that on a board with a single microphone on the left channel only the left half of this register does anything audible; the right channel’s code reads back but has no signal to scale.
- Getter:
Return the volume in dB.
- Setter:
Set both channels to the same volume in dB, clipped to range.
- property bit_clock_inverted: bool
Which BCLK edge the codec latches on.
Only needed if captured words come back rotated by a bit – the datasheet’s external clock timing already matches CircuitPython’s edge convention, so this should not be necessary.
- Getter:
Return True if the bit clock is inverted.
- Setter:
Invert or un-invert the bit clock.
- property bit_depth: int
The I2S word length in bits, as last set by
configure_clocks.- Getter:
Return the configured bit depth.
- chip_id
The device ID register, which always reads
0x1B23.
- config_mic_bias(power_down: bool = False, voltage: int = 6, internal_resistor: bool = False) None
Configure the MICBIAS supply.
- Parameters:
power_down – Power MICBIAS down.
configure_microphone_inputpowers it up, so this is how it goes back off on its own.voltage – Output level select, 0-7: 0 is VDDA, then 1x, 1.1x, 1.2x, 1.3x, 1.4x and 1.53x for 6 and 7. 6 is the reset default and what the microphone path uses.
internal_resistor – Connect the internal 2 kOhm resistor between MICBIAS and MICGND. Only needed if the board does not fit an external one.
- configure_adc_drc(knee1_db: float = -6, knee2_db: float = -20, knee3_db: float = -36, knee4_db: float = -50, limiter_slope: int = 1, compressor1_slope: int = 1, compressor2_slope: int = 1, expander_slope: int = 4, noise_gate_slope: int = 4, attack: int = 5, decay: int = 7, peak_attack: int = 3, peak_decay: int = 4, smooth_filter: bool = True) None
Configure and enable the capture-path dynamic range compressor.
The DRC is a five-section static curve. Reading down from the loudest input: the limiter above
knee1_db, compressor 1 between knee 1 and knee 2, compressor 2 between knee 2 and knee 3, the expander between knee 3 and knee 4, and the noise gate below knee 4. Every argument defaults to the chip’s own reset value, so calling this with no arguments enables the datasheet’s default curve.- Parameters:
knee1_db – Limiter knee, 0 to -31 dB.
knee2_db – Compressor 1 knee, 0 to -63 dB.
knee3_db – Compressor 2 knee, -18 to -81 dB.
knee4_db – Expander knee, -35 to -98 dB.
limiter_slope – Denominator of the limiter’s 1:N ratio: 1 (no limiting), 2, 4, 8, 16, 32, 64, or 0 for a hard limit.
compressor1_slope – Same, but only 1, 2, 4, 8, 16 or 0.
compressor2_slope – Same as
compressor1_slope.expander_slope – Numerator of the expander’s N:1 ratio: 1, 2 or 4. (8 is reserved on the capture path.)
noise_gate_slope – Numerator of the noise gate’s N:1 ratio: 1, 2, 4 or 8.
attack – Attack time code, 0-12. The times are multiples of Ts = 1/sample_rate – 0 is Ts, then 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095 and 8191 Ts. They are codes rather than milliseconds because this driver deliberately does not know the sample rate; see
configure_clocks.decay – Decay time code, 0-10: 63 Ts, then 127, 255, 511, 1023, 2047, 4095, 8191, 16383, 32767 and 65535 Ts.
peak_attack – Peak detector attack code, 0-7, same series as
attack.peak_decay – Peak detector release code, 0-7, same series as
decay.smooth_filter – Round the corners of the static curve at the knee points.
- configure_adc_highpass(frequency: float = 120.0, sample_rate: int = 48000, q: float = 0.7071, enable: bool = True) None
Program the capture biquad as a DC-blocking high-pass filter.
The microphone word carries a large DC offset plus a slow sub-audio bias drift – thousands of counts, and it grows with
mic_gain. Left in, it eats output headroom and, on speech transients, shows up in a passthrough as a loud subsonic “wind” that can drive the output into clipping. This loads BIQ0 with a second-order RBJ high-pass whose exact double zero at DC removes the offset at the source, before the I2S interface, so nothing downstream has to deal with it.Doing it here rather than with a host-side biquad matters: a high-pass at a corner this far below Nyquist has coefficients very close to (1, -2, 1)/(1, -2, 1), and host filters built in fixed point round away the tiny differences that give the stop-band its depth, so they leak tens of dB of sub-bass. This chip runs the section at full internal precision, and forcing the numerator symmetric below keeps the DC null exact even after the coefficients are quantized to the register’s S2.16 format.
- Parameters:
frequency – -3 dB corner in Hz. 120 keeps speech fundamentals while cutting the drift; go higher to cut more low end.
sample_rate – The ADC’s sample rate, i.e. the I2S frame rate the codec is clocked at. Must match it or the corner lands elsewhere.
q – Filter Q. 0.7071 is a maximally flat (Butterworth) response.
enable – Leave the filter switched on afterwards.
- configure_clocks(bit_depth: int = 16, mclk_freq: int | None = None, left_justified: bool = False) None
Configure the codec’s I2S interface and clocking.
The bit clock must already be running when this is called. With the default
mclk_freq=Nonethe codec’s SYSCLK comes from its FLL locked to BCLK, and an FLL with no reference will not lock. Construct theI2SOutobject andplay()a looping sample first, at any sample rate, then call this.- Parameters:
bit_depth – I2S word length: 16, 20, 24 or 32. CircuitPython always sends 16-bit stereo, so leave this at 16.
mclk_freq –
None(the default) locks the FLL to BCLK and leaves the MCLK pin unused.12_000_000instead takes SYSCLK straight from a 12 MHz clock on the MCLK pin with the FLL bypassed, which requiresmicrocontroller.cpu.frequency = 144_000_000and apwmio.PWMOutdriving that pin, and which pins the sample rate at 46875 Hz. The FLL is the better default.left_justified – Use left-justified framing rather than Philips I2S. This is the framing the codec both expects on DACDAT and drives on ADCDAT, so it must match how the
I2SOutobject was built.
- configure_dac_drc(knee1_db: float = -6, knee2_db: float = -20, knee3_db: float = -36, knee4_db: float = -50, limiter_slope: int = 2, compressor1_slope: int = 1, compressor2_slope: int = 1, expander_slope: int = 4, noise_gate_slope: int = 4, attack: int = 5, decay: int = 7, peak_attack: int = 3, peak_decay: int = 4, smooth_filter: bool = True) None
Configure and enable the playback-path dynamic range compressor.
The DRC is a five-section static curve. Reading down from the loudest input: the limiter above
knee1_db, compressor 1 between knee 1 and knee 2, compressor 2 between knee 2 and knee 3, the expander between knee 3 and knee 4, and the noise gate below knee 4. Every argument defaults to the chip’s own reset value, so calling this with no arguments enables the datasheet’s default curve.- Parameters:
knee1_db – Limiter knee, 0 to -31 dB.
knee2_db – Compressor 1 knee, 0 to -63 dB.
knee3_db – Compressor 2 knee, -18 to -81 dB.
knee4_db – Expander knee, -35 to -98 dB.
limiter_slope – Denominator of the limiter’s 1:N ratio: 1 (no limiting), 2, 4, 8, 16, 32, 64, or 0 for a hard limit.
compressor1_slope – Same, but only 1, 2, 4, 8, 16 or 0.
compressor2_slope – Same as
compressor1_slope.expander_slope – Numerator of the expander’s N:1 ratio: 1, 2 or 4. (8 is reserved on the capture path.)
noise_gate_slope – Numerator of the noise gate’s N:1 ratio: 1, 2, 4 or 8.
attack – Attack time code, 0-12. The times are multiples of Ts = 1/sample_rate – 0 is Ts, then 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095 and 8191 Ts. They are codes rather than milliseconds because this driver deliberately does not know the sample rate; see
configure_clocks.decay – Decay time code, 0-10: 63 Ts, then 127, 255, 511, 1023, 2047, 4095, 8191, 16383, 32767 and 65535 Ts.
peak_attack – Peak detector attack code, 0-7, same series as
attack.peak_decay – Peak detector release code, 0-7, same series as
decay.smooth_filter – Round the corners of the static curve at the knee points.
The DAC block’s reset limiter slope is 1:2.
Note that the DRC sits after the crosstalk/sidetone mixer, at the end of the playback path, so a quiet mixed-in signal can be squashed by it.
- configure_microphone_input(gain_db: float = _QUICKSTART_MIC_GAIN_DB, settle: bool = True) None
Power up the microphone-to-ADC path.
This is what
microphone_input = Trueruns; call it directly to choose the front-end gain, or to return immediately instead of waiting out the bias settling time.- Parameters:
gain_db – Front-end PGA gain in dB, -1 to 36. See
mic_gain.settle – Wait ~0.4 s for the microphone bias and the ADC front end to settle before returning. With
settle=Falsethe first few hundred milliseconds of a capture are DC drift with almost no audio in them, so only turn it off if you are going to discard or high-pass that part anyway.
- property dac_biquad: bool
Whether the playback-path biquad filter (BIQ1) is running.
Same caveat as
adc_biquad: zeroed coefficients are silence.- Getter:
True if the DAC biquad is enabled.
- Setter:
Enable or disable the DAC biquad.
- property dac_biquad_coefficients: Tuple[float, ...]
The playback-path biquad coefficients, as
(b0, b1, b2, a1, a2).See
adc_biquad_coefficientsfor the format and its limits.- Getter:
Return the five coefficients as floats.
- Setter:
Write all five in a single ten-register transaction, leaving the enable bit as it was.
- property dac_drc: bool
Whether the playback-path dynamic range compressor is running.
Setting this to True applies the chip’s default curve by calling
configure_dac_drcwith no arguments; use that method to shape it.- Getter:
True if the DAC DRC is enabled.
- Setter:
Enable (with default settings) or disable the DAC DRC.
- property dac_soft_mute: bool
The DAC soft mute, which ramps both channels down together.
This is the only mute the DAC path has – the chip has no per-channel DAC mute bit, so there is no
left_dac_mute/right_dac_mutehere. A single channel can still be silenced through itsleft_dac_channel_volume/right_dac_channel_volume.- Getter:
True if the DAC is muted.
- Setter:
Mute or unmute the DAC.
- property dac_volume: float
The DAC digital volume in dB, averaged across the two channels.
Range is -66 dB (soft) to +24 dB (loud) in 0.5 dB steps; 0 dB is unity. Setting it writes both channels of R34 at once.
This is the level feeding the headphone amplifier, so it interacts with
headphone_volume,headphone_left_mute,headphone_right_muteanddac_soft_mute.- Getter:
Return the volume in dB.
- Setter:
Set both channels to the same volume in dB, clipped to range.
- property headphone_left_mute: bool
The left headphone amplifier mute.
- Getter:
True if the left headphone channel is muted.
- Setter:
Mute or unmute the left headphone channel.
- property headphone_output: bool
Headphone output helper with quickstart default settings.
Setting this to True powers up the whole DAC-to-headphone chain and picks levels intended for quiet listening on sensitive low-impedance earbuds:
dac_volume= -20 dBheadphone_volume= -9 dB (the quietest the analog stage goes)
Setting it to False powers the headphone drivers and the charge pump back down, leaving the microphone path alone.
Either value leaves the microphone path as it found it, so this and
microphone_inputcan be set in either order to run full duplex.configure_clocksmust have been called first – with no SYSCLK the DAC has no clock to run from.- Getter:
True if any of the headphone output drivers are powered.
- Setter:
This sets several properties at once, including both DAC enables, both DAC channel volumes, the headphone analog volume, and the DAC and headphone mutes.
- property headphone_right_mute: bool
The right headphone amplifier mute.
- Getter:
True if the right headphone channel is muted.
- Setter:
Mute or unmute the right headphone channel.
- property headphone_volume: float
The headphone analog volume in dB, averaged across the two channels.
T his stage has only four steps: 0, -3, -6 and -9 dB. Values in between are rounded to the nearest step and values outside the range are clipped, so the level actually applied is worth reading back. Most of the usable attenuation lives in
dac_volume.- Getter:
Return the volume in dB.
- Setter:
Set both channels to the nearest available step.
- property left_dac: bool
The left DAC enabled status.
- Getter:
True if the left DAC is enabled.
- Setter:
Enable or disable the left DAC.
- property left_dac_channel_volume: float
The left DAC channel digital volume in dB, -66 to +24.
- Getter:
Return the volume in dB.
- Setter:
Set the volume in dB, clipped to range.
- property mclk_freq: int | None
The MCLK pin frequency, as last set by
configure_clocks.Nonemeans SYSCLK comes from the FLL locked to BCLK and the MCLK pin is unused.- Getter:
Return the configured MCLK frequency in Hz, or None.
- property mic_gain: float
The analog front-end PGA gain in dB, -1 to 36 in 1 dB steps.
This is the gain applied to the microphone before the ADC, and it is the one that changes the signal-to-noise ratio of a capture – unlike
adc_volume, which scales what the ADC already produced. Setting it writes both channels.- Getter:
Return the left channel’s gain in dB.
- Setter:
Set both channels to the same gain in dB, clipped to range.
- property microphone_input: bool
Microphone input helper with quickstart default settings.
Setting this to True powers up the whole microphone-to-ADC chain – bias, MICBIAS, both front-end PGAs at
mic_gain= 36 dB, the ADC analog front end and both ADC channels – and then waits for the bias to settle before returning. It is equivalent toconfigure_microphone_input(); use that method directly to pick a different gain or to skip the settling delay.Setting it to False powers the microphone path back down, leaving the headphone path alone.
configure_clocksmust have been called first.- Getter:
True if either microphone PGA is powered.
- Setter:
This sets several properties at once, including
mic_gain,adc_volumeand both ADC enables, and it blocks for about 0.4 s while the bias settles.
- reset() None
Reset the codec to its power-on defaults.
It is written twice to clear the internal state machines; one write clears the registers alone.