Add CircuitPython RLCD migration prototype
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
"""CircuitPython audio helpers for Waveshare ESP32-S3-RLCD-4.2.
|
||||
|
||||
Ports the ES8311 setup sequence from MicroPython audio_util.py and adds MP3
|
||||
playback using CircuitPython's audiomp3 + audiobusio stack.
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
import audiobusio
|
||||
import audiomp3
|
||||
import digitalio
|
||||
import pwmio
|
||||
|
||||
|
||||
class ES8311:
|
||||
ADDR = 0x18
|
||||
|
||||
def __init__(self, i2c):
|
||||
self.i2c = i2c
|
||||
|
||||
def _write(self, register, value):
|
||||
# CircuitPython I2CDevice is not used to keep this dependency-free.
|
||||
while not self.i2c.try_lock():
|
||||
pass
|
||||
try:
|
||||
self.i2c.writeto(self.ADDR, bytes([register & 0xFF, value & 0xFF]))
|
||||
finally:
|
||||
self.i2c.unlock()
|
||||
|
||||
def init(self, sample_rate=44100):
|
||||
"""Initialize ES8311 for I2S DAC playback.
|
||||
|
||||
The register sequence is the existing MicroPython project's known-good
|
||||
sequence. The original comments targeted 16 kHz WAV playback, but the
|
||||
I2S peripheral is configured with the MP3 decoder sample rate at play
|
||||
time. If MP3 output is silent, hardware-test this sequence first with a
|
||||
16 kHz WAV/tone and then tune codec dividers for the MP3 sample rate.
|
||||
"""
|
||||
del sample_rate # Kept for API clarity; sequence currently fixed.
|
||||
self._write(0x00, 0x1F)
|
||||
time.sleep(0.01)
|
||||
self._write(0x00, 0x00)
|
||||
time.sleep(0.01)
|
||||
self._write(0x01, 0x3F)
|
||||
self._write(0x02, 0x48)
|
||||
self._write(0x03, 0x10)
|
||||
self._write(0x04, 0x20)
|
||||
self._write(0x05, 0x00)
|
||||
self._write(0x06, 0x03)
|
||||
self._write(0x07, 0x00)
|
||||
self._write(0x08, 0xFF)
|
||||
self._write(0x09, 0x0C)
|
||||
self._write(0x0A, 0x0C)
|
||||
self._write(0x0D, 0x01)
|
||||
self._write(0x0E, 0x02)
|
||||
self._write(0x12, 0x00)
|
||||
self._write(0x13, 0x10)
|
||||
self._write(0x1C, 0x6A)
|
||||
self._write(0x37, 0x08)
|
||||
self._write(0x32, 0xBF)
|
||||
self._write(0x31, 0x00)
|
||||
self._write(0x00, 0x80)
|
||||
|
||||
def set_volume(self, volume):
|
||||
volume = max(0, min(100, int(volume)))
|
||||
self._write(0x32, int(volume * 255 / 100))
|
||||
|
||||
|
||||
class BoardAudio:
|
||||
def __init__(self, i2c, *, bit_clock, word_select, data, mclk, amp):
|
||||
self.i2c = i2c
|
||||
self.bit_clock_pin = bit_clock
|
||||
self.word_select_pin = word_select
|
||||
self.data_pin = data
|
||||
self.mclk_pin = mclk
|
||||
self.amp_pin = amp
|
||||
self._mclk = None
|
||||
self._amp = digitalio.DigitalInOut(amp)
|
||||
self._amp.switch_to_output(value=False)
|
||||
self.codec = ES8311(i2c)
|
||||
|
||||
def start_mclk(self):
|
||||
if self._mclk is None:
|
||||
# External MCLK for ES8311. CircuitPython I2SOut on ESP32-S3 does
|
||||
# not accept a main_clock parameter, so keep this independent.
|
||||
self._mclk = pwmio.PWMOut(
|
||||
self.mclk_pin,
|
||||
frequency=12288000,
|
||||
duty_cycle=32768,
|
||||
variable_frequency=False,
|
||||
)
|
||||
|
||||
def stop_mclk(self):
|
||||
if self._mclk is not None:
|
||||
self._mclk.deinit()
|
||||
self._mclk = None
|
||||
|
||||
def play_mp3(self, filename, volume=60):
|
||||
"""Play an MP3 file from CIRCUITPY storage over the onboard speaker."""
|
||||
self.start_mclk()
|
||||
self.codec.init()
|
||||
self.codec.set_volume(volume)
|
||||
decoder = None
|
||||
audio = None
|
||||
f = None
|
||||
try:
|
||||
f = open(filename, "rb")
|
||||
decoder = audiomp3.MP3Decoder(f)
|
||||
# Use decoder-derived rate if available, else fall back.
|
||||
sample_rate = getattr(decoder, "sample_rate", 44100)
|
||||
audio = audiobusio.I2SOut(
|
||||
bit_clock=self.bit_clock_pin,
|
||||
word_select=self.word_select_pin,
|
||||
data=self.data_pin,
|
||||
)
|
||||
self._amp.value = True
|
||||
audio.play(decoder)
|
||||
while audio.playing:
|
||||
time.sleep(0.05)
|
||||
return True
|
||||
finally:
|
||||
self._amp.value = False
|
||||
if audio is not None:
|
||||
audio.deinit()
|
||||
if decoder is not None:
|
||||
decoder.deinit()
|
||||
if f is not None:
|
||||
f.close()
|
||||
# Leave MCLK running for repeated playback; call stop_mclk() before
|
||||
# deep sleep if power matters.
|
||||
Reference in New Issue
Block a user