Expose speaker playback and play_tone tool via MCP
This commit is contained in:
+157
@@ -126,3 +126,160 @@ def record_audio(duration_seconds=5, filename='recording.pcm'):
|
||||
# Always release the I2S peripheral resources
|
||||
i2s.deinit()
|
||||
print("I2S receiver deinitialized.")
|
||||
|
||||
|
||||
class ES8311:
|
||||
"""MicroPython driver for the ES8311 Audio Codec (Speaker DAC).
|
||||
|
||||
Controls the ES8311 chip over I2C to configure clocks, audio format, and volume.
|
||||
"""
|
||||
ADDR = 0x18
|
||||
|
||||
def __init__(self, i2c):
|
||||
self.i2c = i2c
|
||||
|
||||
def init(self, sample_rate=16000):
|
||||
"""Initializes the ES8311 registers for audio playback.
|
||||
|
||||
Args:
|
||||
sample_rate (int): Audio sample rate (typically 16000).
|
||||
|
||||
Returns:
|
||||
bool: True if initialization was successful, False otherwise.
|
||||
"""
|
||||
print("Initializing ES8311 Speaker DAC...")
|
||||
try:
|
||||
# 1. Reset the chip
|
||||
self._write(0x00, 0x1F)
|
||||
time.sleep_ms(10)
|
||||
self._write(0x00, 0x00)
|
||||
time.sleep_ms(10)
|
||||
|
||||
# Clock Configuration (16kHz sample rate, MCLK=12.288MHz)
|
||||
self._write(0x01, 0x3F) # Enable all clocks, use MCLK pin
|
||||
self._write(0x02, 0x48) # pre_div=3, pre_mult=1
|
||||
self._write(0x03, 0x10) # fs_mode=0, adc_osr=16
|
||||
self._write(0x04, 0x20) # dac_osr=32
|
||||
self._write(0x05, 0x00) # adc_div=1, dac_div=1
|
||||
self._write(0x06, 0x03) # bclk_div=4 (4-1=3)
|
||||
self._write(0x07, 0x00) # lrck_h=0
|
||||
self._write(0x08, 0xFF) # lrck_l=255
|
||||
|
||||
# Audio Format Configuration (I2S standard format, 16-bit)
|
||||
self._write(0x09, 0x0C) # SDP in: 16-bit I2S
|
||||
self._write(0x0A, 0x0C) # SDP out: 16-bit I2S
|
||||
|
||||
# System / DAC Power Up
|
||||
self._write(0x0D, 0x01) # Power up analog circuitry
|
||||
self._write(0x0E, 0x02) # Enable analog PGA, enable ADC modulator
|
||||
self._write(0x12, 0x00) # Power up DAC
|
||||
self._write(0x13, 0x10) # Enable output to HP drive (speaker/hp output)
|
||||
self._write(0x1C, 0x6A) # ADC Equalizer bypass
|
||||
self._write(0x37, 0x08) # Bypass DAC equalizer
|
||||
|
||||
# Set Volume (0xBF = 0dB)
|
||||
self._write(0x32, 0xBF)
|
||||
|
||||
# Unmute DAC
|
||||
self._write(0x31, 0x00)
|
||||
|
||||
# Power On
|
||||
self._write(0x00, 0x80)
|
||||
|
||||
print("ES8311 initialization complete.")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Failed to initialize ES8311: {e}")
|
||||
return False
|
||||
|
||||
def set_volume(self, val):
|
||||
"""Sets DAC digital volume (0-100 scale)."""
|
||||
# Volume register 0x32 accepts values from 0 (mute) to 255 (+0dB / max volume).
|
||||
reg_val = int((val / 100.0) * 255.0)
|
||||
reg_val = max(0, min(255, reg_val))
|
||||
try:
|
||||
self._write(0x32, reg_val)
|
||||
except Exception as e:
|
||||
print(f"Failed to set volume: {e}")
|
||||
|
||||
def _write(self, reg, val):
|
||||
self.i2c.writeto_mem(self.ADDR, reg, bytes([val]))
|
||||
|
||||
|
||||
def play_tone(frequency=440, duration_ms=1000, volume=50):
|
||||
"""Plays a pure sine wave tone on the board speaker.
|
||||
|
||||
Args:
|
||||
frequency (int): Tone frequency in Hz (e.g. 440 for A4).
|
||||
duration_ms (int): Tone duration in milliseconds.
|
||||
volume (int): Volume level from 0 to 100.
|
||||
"""
|
||||
import math
|
||||
import struct
|
||||
|
||||
print(f"Playing tone: {frequency}Hz for {duration_ms}ms (vol={volume})...")
|
||||
|
||||
# 1. Setup Master Clock (MCLK) on GPIO 16 using PWM
|
||||
mclk_pin = Pin(16, Pin.OUT)
|
||||
mclk_pwm = machine.PWM(mclk_pin)
|
||||
mclk_pwm.freq(12288000)
|
||||
mclk_pwm.duty_u16(32768)
|
||||
|
||||
# 2. Start I2C Control Bus (SDA=13, SCL=14)
|
||||
i2c = I2C(0, sda=Pin(13), scl=Pin(14))
|
||||
|
||||
# 3. Initialize the ES8311 DAC
|
||||
dac = ES8311(i2c)
|
||||
if not dac.init(sample_rate=16000):
|
||||
mclk_pwm.deinit()
|
||||
return False
|
||||
|
||||
dac.set_volume(volume)
|
||||
|
||||
# 4. Configure I2S TX
|
||||
# Pins: sck=BCLK (GPIO 9), ws=WS/LRCK (GPIO 45), sd=DOUT (GPIO 8)
|
||||
i2s = I2S(1,
|
||||
sck=Pin(9),
|
||||
ws=Pin(45),
|
||||
sd=Pin(8),
|
||||
mode=I2S.TX,
|
||||
ibuf=8000,
|
||||
rate=16000,
|
||||
bits=16,
|
||||
format=I2S.STEREO)
|
||||
|
||||
# 5. Enable Speaker Amplifier (GPIO 46)
|
||||
amp_pin = Pin(46, Pin.OUT, value=1)
|
||||
|
||||
# 6. Generate sine wave cycle
|
||||
# Approximate frequency to make integer number of samples per cycle
|
||||
# (avoiding phase clicking)
|
||||
N = int(16000 / frequency)
|
||||
N = max(4, N) # prevent division by zero or extremely high frequencies
|
||||
|
||||
volume_scale = int((volume / 100.0) * 32767)
|
||||
|
||||
cycle_data = bytearray()
|
||||
for i in range(N):
|
||||
val = int(volume_scale * math.sin(2 * math.pi * i / N))
|
||||
cycle_data.extend(struct.pack("<hh", val, val)) # Stereo (L/R)
|
||||
cycle_bytes = bytes(cycle_data)
|
||||
|
||||
# Write to I2S in chunks
|
||||
total_samples = int(16000 * duration_ms / 1000)
|
||||
total_cycles = int(total_samples / N)
|
||||
|
||||
written_cycles = 0
|
||||
while written_cycles < total_cycles:
|
||||
cycles_to_write = min(total_cycles - written_cycles, 100)
|
||||
i2s.write(cycle_bytes * cycles_to_write)
|
||||
written_cycles += cycles_to_write
|
||||
|
||||
# 7. Clean up
|
||||
time.sleep_ms(100) # Let the remaining buffer play out
|
||||
amp_pin.value(0)
|
||||
i2s.deinit()
|
||||
mclk_pwm.deinit()
|
||||
print("Tone playback complete.")
|
||||
return True
|
||||
|
||||
|
||||
@@ -217,6 +217,18 @@ class MCPServer:
|
||||
},
|
||||
"required": ["code"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "play_tone",
|
||||
"description": "Play a pure sine wave tone/beep on the board speaker.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"frequency": {"type": "integer", "description": "Frequency of the tone in Hz (e.g. 440 for A4, default 440)", "default": 440},
|
||||
"duration_ms": {"type": "integer", "description": "Duration of the tone in milliseconds (default 1000)", "default": 1000},
|
||||
"volume": {"type": "integer", "description": "Volume of the tone from 0 to 100 (default 50)", "default": 50}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -407,5 +419,19 @@ class MCPServer:
|
||||
return output
|
||||
return f"Execution Succeeded. Console output:\n{output}"
|
||||
|
||||
elif name == "play_tone":
|
||||
freq = int(args.get("frequency", 440))
|
||||
duration = int(args.get("duration_ms", 1000))
|
||||
vol = int(args.get("volume", 50))
|
||||
|
||||
# Limit duration to prevent blocking the main loop for too long
|
||||
duration = max(50, min(5000, duration))
|
||||
freq = max(50, min(10000, freq))
|
||||
vol = max(0, min(100, vol))
|
||||
|
||||
from audio_util import play_tone
|
||||
play_tone(frequency=freq, duration_ms=duration, volume=vol)
|
||||
return f"Played tone of {freq}Hz for {duration}ms at volume {vol}."
|
||||
|
||||
else:
|
||||
raise ValueError(f"Unknown tool: {name}")
|
||||
|
||||
Reference in New Issue
Block a user