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
|
# Always release the I2S peripheral resources
|
||||||
i2s.deinit()
|
i2s.deinit()
|
||||||
print("I2S receiver deinitialized.")
|
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"]
|
"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 output
|
||||||
return f"Execution Succeeded. Console output:\n{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:
|
else:
|
||||||
raise ValueError(f"Unknown tool: {name}")
|
raise ValueError(f"Unknown tool: {name}")
|
||||||
|
|||||||
Reference in New Issue
Block a user