For our interactive project, Daniel and I decided to create a Donald Trump voodoo doll. When you picked him up, he would speak one of his lines stored in the SD card in the QT PY. The code would detect when a charge (human touch) would interact with the “SDA” pin on the QT PY.
Image Board (for easier access)
Materials:
QT PY + Audio BFF + SD Card
Trump Plush
Speaker
Battery Pack
Tape
Step-by-step Guide:
Step 1: Upload desired .wav files onto the SD card (formatted as FAT32)
Step 2: Upload code onto the QT PY
Step 3: Solder the Audio BFF and QT PY together, and slot the SD card in
Step 4: Solder the battery pack onto the QT PY and connect the speaker onto the Audio BFF
Step 5: Hollow out the Plush of stuffing to place the full electronic combo inside, with the QT PY with the “SDA” pin facing the outside
Step 6: Close the plush with tape (sewing kit or other closure method)!
Code:
// QT Py ESP32 Pico + Audio BFF — minimal WAV sequencer
// Plays /track1.wav .. /track5.wav in order, 2 sec gap in between, then loops.
// Requires: WAV audio files, PCM 16-bit, stereo, 44.1 kHz with a 44-byte header.
// SD card must be formatted as FAT32 – not ExFAT. ExFAT wont work.
#include <Arduino.h>
#include <SPI.h>
#include <SD.h>
#include “driver/i2s.h”
// —- Pin assignments on QTPY using Audio BFF defaults —-
#define SD_CS A0
#define I2S_DOUT A1
#define I2S_LRC A2
#define I2S_BCLK A3
// —- I2S setup (fixed file type: 44.1 kHz, 16-bit, stereo) —-
static const i2s_port_t I2S_PORT = I2S_NUM_0;
static i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
.sample_rate = 44100,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 8,
.dma_buf_len = 256,
.use_apll = false,
.tx_desc_auto_clear = true,
.fixed_mclk = -1
};
static i2s_pin_config_t i2s_pins = {
.bck_io_num = I2S_BCLK,
.ws_io_num = I2S_LRC,
.data_out_num = I2S_DOUT,
.data_in_num = I2S_PIN_NO_CHANGE
};
// —- sound stream buffer —-
uint8_t buf[1024]; // multiple of 4 bytes (stereo 16-bit frames)
// —- Play one audio file at a time, w/ 2 sec delay in between
void playWavFixedFormat(const char* path) {
File f = SD.open(path, FILE_READ);
if (!f) {
Serial.print(“Missing file: “); Serial.println(path);
return;
}
// Skip the 44-byte WAV header
if (!f.seek(44)) {
Serial.print(“Seek failed (header) for “); Serial.println(path);
f.close();
return;
}
Serial.print(“Playing “); Serial.println(path);
while (true) {
size_t n = f.read(buf, sizeof(buf));
if (n == 0) break; // EOF
size_t written = 0; // Write whole chunk to I2S (blocking)
if (i2s_write(I2S_PORT, (const char*)buf, n, &written, portMAX_DELAY) != ESP_OK) {
Serial.println(“i2s_write error”);
break;
}
if (written != n) { // Shouldn’t happen with portMAX_DELAY, but bail if it does
Serial.println(“Short write to I2S”);
break;
}
}
f.close();
}
void setup() {
Serial.begin(115200);
while (!Serial) { delay(10); }
Serial.println(“QT Py ESP32 Pico + Audio BFF Player”);
pinMode(SD_CS, OUTPUT); // —- SD init —-
digitalWrite(SD_CS, HIGH);
SPI.begin();
if (!SD.begin(SD_CS, SPI, 10000000)) { // slightly slower SPI can help some cards/boards; 10 MHz here
Serial.println(“SD begin failed. Make sure SD card is MS-DOS (FAT)/FAT32 and CS=A0.”);
for(;;) delay(1000);
}
Serial.println(“SD OK.”);
if (i2s_driver_install(I2S_PORT, &i2s_config, 0, nullptr) != ESP_OK) { // —- I2S init —-
Serial.println(“i2s_driver_install failed”);
for(;;) delay(1000);
}
if (i2s_set_pin(I2S_PORT, &i2s_pins) != ESP_OK) {
Serial.println(“i2s_set_pin failed”);
for(;;) delay(1000);
}
// Confirm clock (optional, redundant because sample_rate already set)
i2s_set_clk(I2S_PORT, 44100, I2S_BITS_PER_SAMPLE_16BIT, I2S_CHANNEL_STEREO);
}
void loop() {
for (int i = 1; i <= 5; i++) { // Play track1 – track5 with 2-second gaps
char path[16];
snprintf(path, sizeof(path), “/track%d.wav”, i);
playWavFixedFormat(path);
delay(2000); // 2 sec gap
}
// Loop forever
}