Community project

AI Companion Head

M_Two

Published July 31, 2026

ESP325 components6 assembly steps
Remix this project
Photo of AI Companion Head

Build an AI Companion Head that displays animated faces on an OLED screen and responds with synthesized speech through a speaker. This ESP32-based project receives commands from a PC over Wi-Fi, allowing the companion to show expressions and play audio generated by an AI model running on the host computer.

The guide provides a complete wiring diagram connecting the SSD1306 display, INMP441 microphone, MAX98357A amplifier, speaker, and MicroSD card module to the ESP32. Assembly steps cover safe wiring practices, firmware configuration for Wi-Fi and audio settings, and customization of facial animations through the provided C++ code.

Wiring diagram

Interactive · read-only
Wiring diagram for AI Companion Head

Pan and zoom to explore the wiring. Remix the project to edit it in your own workspace.

Parts list

Bill of materials
ComponentQtyNotes
SSD1306 OLED128×64 monochrome I2C10.96 inch 128x64 OLED display with I2C interface
HiLetgo INMP441 I²S Microphone ModuleI2S microphone1Omnidirectional 24-bit I²S MEMS microphone module based on the TDK InvenSense INMP441 IC. Outputs digital audio over a 3-wire I²S bus (SCK/BCLK, WS/LRCLK, SD/DOUT). Supply 1.8–3.3 V; native 3.3 V operation with no level shifting required. L/R channel select pin: tie to GND for left-channel mono output or VDD for right-channel mono output. Use the ESP32 Arduino core or ESP-IDF I2S peripheral API; no separate PlatformIO library is required.
MAX98357A I2S Class-D Mono Amplifier Breakoutmono I2S amplifier1I2S-input Class-D mono audio amplifier IC on a compact breakout board. Accepts I2S digital audio input (BCLK, LRC, DIN) and drives a small speaker or transducer directly. No I2C/SPI control bus is needed. The amplifier supply range is 2.5V-5.5V, and the I2S input pins are compatible with 3.3V logic. SD/MODE controls shutdown and channel selection; GAIN selects 3 dB, 6 dB, 9 dB, 12 dB, or 15 dB gain.
8Ω Speaker8 Ω, 1 W1Generic small 8Ω 0.5-3W loudspeaker (~28mm typical). Pair with an I2S amp (MAX98357A) or class-D amp (TPA3116D2) for usable volume; do not drive directly from a GPIO pin. Audio output for music/voice playback.
MicroSD Card ModuleSPI microSD adapter1SPI-based microSD card adapter module for SPI-capable microcontrollers. Uses MOSI, MISO, SCK, and CS plus power and ground. Many low-cost modules include a 3.3 V regulator and level shifting for 5 V MCU boards, while bare breakouts should be powered and signalled at 3.3 V.

Assembly

6 steps
  1. Keep USB disconnected while wiring

    Build the circuit on a breadboard or secure wiring harness first. Use the ESP32 USB port as the only power source for this version.

    • Tip: Use short wires for I²S audio connections to reduce noise.
    • Tip: All modules must share ESP32 GND.
    • Do not power the INMP441 microphone from 5 V.
    • Do not connect either speaker terminal to GND; the MAX98357A speaker outputs are a paired amplifier output.
  2. Wire the OLED screen

    Connect OLED VCC to ESP32 3V3 and OLED GND to ESP32 GND. Connect OLED SDA to GPIO21 and OLED SCL to GPIO22.

    • Tip: This assumes the selected 128×64 I²C SSD1306 OLED with address 0x3C.
    • Tip: If the screen remains blank, confirm its VCC/GND labels and I²C address before changing any wiring.
    • Use 3.3 V for the OLED to keep its I²C signals safe for the ESP32.
  3. Wire the I²S microphone

    Connect INMP441 VDD to 3V3, GND to GND, and L/R to GND (left channel). Connect SCK to GPIO32, WS to GPIO33, and SD to GPIO16.

    • Tip: Place the microphone away from the speaker so it is less likely to hear its own reply audio.
    • INMP441 VDD must be 3.3 V maximum.
  4. Wire amplifier and speaker

    Connect MAX98357A VIN to ESP32 5V/VIN, and GND to ESP32 GND. Connect BCLK to GPIO26, LRC to GPIO25, and DIN to GPIO27. Connect amplifier SPK+ to speaker POS and amplifier SPK- to speaker NEG.

    • Tip: The amplifier is supplied from USB's 5 V rail for useful speaker volume.
    • Tip: Keep the two speaker leads together and separate from microphone wires where possible.
    • Never connect SPK+ or SPK- to the ESP32, 5 V, or GND.
    • Start with a 1 W or higher 8 Ω speaker.
  5. Wire the microSD adapter

    Connect SD adapter VCC to 3V3 and GND to GND. Connect MISO to GPIO19, MOSI to GPIO23, SCK to GPIO18, and CS to GPIO4. Insert a FAT32-formatted microSD card after wiring.

    • Tip: Use an adapter designed for 3.3 V SPI logic, or one explicitly marked safe with 3.3 V ESP32 signals.
    • Do not use a 5 V-only SD module with 3.3 V ESP32 signal pins.
  6. Power the body and connect the PC

    Connect USB power and use Schematik’s Deploy button. The ESP32 creates Wi-Fi named Companion-Body. Connect the PC running the companion program to that network, then let the PC send display-state, text, and audio commands to the ESP32 at 192.168.4.1.

    • Tip: The network password and other body settings are in src/companion_settings.h.
    • Tip: The PC program is the brain: it performs AI, speech recognition, text-to-speech, and decides what the OLED shows.
    • Change the default Wi-Fi password in companion_settings.h before regular use.
    • This SSD1306 screen is monochrome. The PC can select face animations and text, but it cannot mirror a colour picture or normal GIF exactly.

Pin assignments

Board wiring reference
PinConnectionType
3V3oled_1 VCCpower
GNDoled_1 GNDground
GPIO 21oled_1 SDAi2c
GPIO 22oled_1 SCLi2c
3V3mic_1 VDDpower
GNDmic_1 GNDground
GNDmic_1 L/Rground
5Vamp_1 VINpower
GNDamp_1 GNDground
GPIO 26amp_1 BCLKdata
GPIO 25amp_1 LRCdata
GPIO 27amp_1 DINdata
EXTamp_1 SPK+8Ω Speaker POSdata
EXTamp_1 SPK-8Ω Speaker NEGdata
3V3sd_1 VCCpower
GNDsd_1 GNDground
GPIO 19sd_1 MISOspi
GPIO 23sd_1 MOSIspi
GPIO 18sd_1 SCKspi
GPIO 32mic_1 SCKdigital
GPIO 33mic_1 WSdigital
GPIO 16mic_1 SDdata
GPIO 4sd_1 CSspi

Firmware

ESP32
main.cppDeploy to device
// PC Mirror Companion — ESP32 body firmware
// GENERATE: the PC's AI sends state/text/audio commands below.
// MODIFY: edit face and animation drawing in companion_body.cpp.
// SETTINGS: edit Wi-Fi and audio values in companion_settings.h.
#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
#include "driver/i2s.h"
#include "companion_settings.h"
#include "companion_body.h"


// Forward declarations
void startSpeaker();
void sendJson(const String &json);
void handleStatus();
void handleSet();
void handleSpeak();
void handleRoot();

constexpr int AMP_BCLK = 26;
constexpr int AMP_LRCLK = 25;
constexpr int AMP_DIN = 27;
WebServer server(BODY_HTTP_PORT);

void startSpeaker() {
  i2s_config_t config = {};
  config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX);
  config.sample_rate = PCM_SAMPLE_RATE;
  config.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT;
  config.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT;
  config.communication_format = I2S_COMM_FORMAT_I2S;
  config.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1;
  config.dma_buf_count = 4;
  config.dma_buf_len = 128;
  config.tx_desc_auto_clear = true;
  i2s_driver_install(I2S_NUM_1, &config, 0, nullptr);
  i2s_pin_config_t pins = {};
  pins.bck_io_num = AMP_BCLK;
  pins.ws_io_num = AMP_LRCLK;
  pins.data_out_num = AMP_DIN;
  pins.data_in_num = I2S_PIN_NO_CHANGE;
  i2s_set_pin(I2S_NUM_1, &pins);
  i2s_zero_dma_buffer(I2S_NUM_1);
}

void sendJson(const String &json) {
  server.sendHeader("Access-Control-Allow-Origin", "*");
  server.send(200, "application/json", json);
}
void handleStatus() {
  String json = "{\"state\":\"" + bodyStateName() + "\",\"text\":\"" + bodyText() +
                "\",\"ip\":\"" + WiFi.softAPIP().toString() + "\"}";
  sendJson(json);
}
void handleSet() {
  if (!server.hasArg("state") && !server.hasArg("text")) {
    server.send(400, "text/plain", "Use state and/or text query values.");
    return;
  }
  if (server.hasArg("state")) bodySetState(server.arg("state"));
  if (server.hasArg("text")) bodySetText(server.arg("text"));
  if (server.hasArg("contrast")) bodySetContrast((uint8_t)constrain(server.arg("contrast").toInt(), 0, 255));
  handleStatus();
}
void handleSpeak() {
  // PC TTS sends raw signed 16-bit, 16 kHz, mono PCM in the POST body.
  if (server.method() != HTTP_POST) {
    server.send(405, "text/plain", "POST raw 16-bit mono PCM data.");
    return;
  }
  bodySetState("talking");
  const String &raw = server.arg("plain");
  const uint8_t *bytes = (const uint8_t *)raw.c_str();
  for (size_t offset = 0; offset + 1 < raw.length(); offset += 2) {
    int16_t sample = (int16_t)((uint16_t)bytes[offset] | ((uint16_t)bytes[offset + 1] << 8));
    int16_t stereo[2] = {sample, sample};
    size_t written;
    i2s_write(I2S_NUM_1, stereo, sizeof(stereo), &written, portMAX_DELAY);
  }
  bodySetState("idle");
  sendJson("{\"ok\":true}");
}
void handleRoot() {
  server.send(200, "text/html", "<h2>PC Mirror Companion</h2><p>PC control endpoint ready.</p><p>GET /set?state=listening&text=Hello</p><p>GET /status</p>");
}

void setup() {
  Serial.begin(115200);
  bodyBegin();
  startSpeaker();
  WiFi.mode(WIFI_AP);
  WiFi.softAP(BODY_AP_NAME, BODY_AP_PASSWORD);
  bodySetText("Connect PC to Wi-Fi");
  server.on("/", HTTP_GET, handleRoot);
  server.on("/status", HTTP_GET, handleStatus);
  server.on("/set", HTTP_GET, handleSet);
  server.on("/speak", HTTP_POST, handleSpeak);
  server.begin();
}
void loop() {
  server.handleClient();
  bodyTick();
}

“Deploy to device” opens this project in Schematik, where you can flash it to your board over USB.

Remix this project

Make it yours in one click

Open a full copy of this project in your own Schematik workspace — diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.

Open in Schematik