DIY EMO Robot – ESP32, OLED Ekran ve 3D Baskı ile Akıllı Masaüstü Robotu
YouTube kanalımızda adım adım yapılışını paylaştığımız sevimli masaüstü robotumuz EMO'nun tüm açık kaynak tasarım dosyaları, 3D baskı STL modelleri, ESP32 Arduino yazılımları, devre pin şeması ve ses efektleri paketi.
YouTube Video Serisi & Tanıtım
Kanala Abone OlYouTube kanalımızda başlattığımız "ESP32 ile EMO Robot Yapıyoruz!" serisinde, robotun 3D modellemesinden dilimlenmesine, OLED göz animasyonlarının matematiksel hesaplarından (ease-in-out yumuşak geçişler ve squash-stretch göz kırpmalar) I2S DAC ses modülü ile LittleFS üzerinden gerçek zamanlı WAV sesleri çalınmasına kadar tüm adımları detaylıca inceliyoruz.
İnteraktif 3D STL Kafa Modeli Önizleyici
Modeli fareyle (sol tık ile) 360° döndürebilir, tekerlekle yakınlaştırabilir ve sağ tık ile kaydırabilirsiniz.
Gerekli Malzeme Listesi (BOM)
Projeyi hayata geçirmek için ihtiyaç duyacağınız elektronik donanımlar, sensörler ve mekanik bileşenlerin listesi:
| Bileşen / Malzeme | Açıklama / Model | Adet | İşlevi |
|---|---|---|---|
| ESP32 NodeMCU | ESP-WROOM-32 (30 veya 38 Pin) | 1 Adet | Ana beyin, OLED göz kontrolü, I2S ses çözücü ve durum makinesi. |
| 0.96" OLED Ekran | SSD1306 128x64 Piksel (I2C Mavi veya Beyaz) | 1 Adet | Robotun yüzü; göz ifadeleri, uykulu/mutlu bakış animasyonları. |
| MAX98357A I2S DAC | 3W D-Sınıfı Dijital Ses Amfisi | 1 Adet | ESP32'den saf dijital ses sinyali alıp hoparlöre temiz ses iletir. |
| Mikro Hoparlör | 4Ω veya 8Ω, 2W-3W (Minyatür 20-30mm) | 1 Adet | EMO'nun ses tonları ve WAV selamlama seslerini dışa vurur. |
| Dokunmatik Sensör | TTP223 Kapasitif Dokunmatik Sensör (veya Touch Pin) | 1 Adet | Baş okşama ve kafaya dokunma tepkilerini algılar (GPIO 4). |
| Mikro Servo Motor | SG90 veya MG90S Metal Dişli (Opsiyonel) | 1-2 Adet | Kafa sağ-sol veya yukarı-aşağı jest hareketleri için. |
| Güç Kaynağı | Micro-USB / Type-C 5V 1A veya 18650 Pil + TP4056 | 1 Adet | Sisteme kesintisiz ve kararlı 5V besleme sağlar. |
| 3D Gövde Parçaları | PLA / PETG Filament Baskı | 1 Set | İç donanımı koruyan kafa ve şasi kabuğu. |
| Bağlantı Elemanları | Jumper Kablolar + M2 / M3 Cıvata Somun | 1 Set | Donanımların sabitlenmesi ve pin bağlantıları. |
Devre Bağlantı Şeması ve Pinout Tablosu
Devre bağlantılarını yaparken ESP32 pin numaralarına dikkat ediniz. Aşağıdaki pin tablosu hem OLED hem I2S DAC ses çipi için çakışma olmadan optimize edilmiştir:
| Modül | Modül Pini | ESP32 GPIO | Sinyal Türü | Notlar |
|---|---|---|---|---|
| SSD1306 OLED | VCC | 3.3V | Güç | Ekran voltajı (bazı modüller 5V ile de çalışır) |
| SSD1306 OLED | GND | GND | Toprak | Ortak şase hattı |
| SSD1306 OLED | SCL | GPIO 22 | I2C Clock | Donanımsal I2C saat hattı |
| SSD1306 OLED | SDA | GPIO 21 | I2C Data | Donanımsal I2C veri hattı |
| MAX98357A Ses | VIN / VDD | 5V (VIN) | Güç | Hoparlör gücü için 5V önerilir |
| MAX98357A Ses | GND | GND | Toprak | Ortak şase hattı |
| MAX98357A Ses | BCLK | GPIO 26 | I2S Bit Clock | Dijital ses bit saat frekansı |
| MAX98357A Ses | LRC / WS | GPIO 25 | I2S Word Select | Sol/Sağ kanal seçici |
| MAX98357A Ses | DIN / SD | GPIO 27 | I2S Serial Data | Ses dalga verisi iletimi |
| MAX98357A Ses | GAIN | Boşta (GND) | Kazanç Ayarı | Boş bırakıldığında varsayılan +9dB kazanç |
| Dokunmatik Sensör | SIG (Çıkış) | GPIO 4 | Touch Giriş | Kafa dokunmatik algılama (Touch0) |
EMO Ses Efektleri Paketi (LittleFS)
Robotun durumlarına göre çaldığı orijinal ses dosyaları (16-bit PCM WAV, 16000Hz Mono). Tarayıcınızda canlı dinleyebilirsiniz:
Arduino & ESP32 Kaynak Kodları
Proje için hazırladığımız iki temel yazılımı aşağıdan inceleyebilir, kopyalayabilir veya doğrudan .ino olarak bilgisayarınıza indirebilirsiniz:
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
// Göz boyutları
const int EYE_W = 28;
const int EYE_H = 28;
const int EYE_RADIUS = 8;
const int LEFT_EYE_BASE_X = 22;
const int RIGHT_EYE_BASE_X = 78;
const int EYE_BASE_Y = 18;
// Mevcut ve hedef göz pozisyonu (float -> yumuşak hareket için)
float currentX = 0, currentY = 0;
float targetX = 0, targetY = 0;
// Göz yükseklik oranı (blink için, squash/stretch)
float eyeHeightScale = 1.0; // 1.0 = tam açık, 0.0 = tamamen kapalı
void drawEyes(float xOff, float yOff, float heightScale)
{
display.clearDisplay();
int h = max(2, (int)(EYE_H * heightScale));
int yAdjust = (EYE_H - h) / 2;
int leftEyeX = LEFT_EYE_BASE_X + (int)xOff;
int rightEyeX = RIGHT_EYE_BASE_X + (int)xOff;
int eyeY = EYE_BASE_Y + (int)yOff + yAdjust;
int radius = min(EYE_RADIUS, h / 2);
display.fillRoundRect(leftEyeX, eyeY, EYE_W, h, radius, SSD1306_WHITE);
display.fillRoundRect(rightEyeX, eyeY, EYE_W, h, radius, SSD1306_WHITE);
display.display();
}
// Ease-in-out: yavaş başlar, hızlanır, yavaşlayarak biter (gerçek göz kası hareketi gibi)
float easeInOutQuad(float t)
{
if (t < 0.5) return 2 * t * t;
return 1 - pow(-2 * t + 2, 2) / 2;
}
// Gözleri hedef konuma yumuşak şekilde hareket ettirir
void moveEyesTo(float newTargetX, float newTargetY, int durationMs = 220)
{
float startX = currentX;
float startY = currentY;
int steps = max(6, durationMs / 12);
for (int i = 1; i <= steps; i++)
{
float t = (float)i / steps;
float easedT = easeInOutQuad(t);
currentX = startX + (newTargetX - startX) * easedT;
currentY = startY + (newTargetY - startY) * easedT;
drawEyes(currentX, currentY, eyeHeightScale);
delay(12);
}
currentX = newTargetX;
currentY = newTargetY;
drawEyes(currentX, currentY, eyeHeightScale);
}
// Göz Kırpma (Blink) animasyonu
void blinkEyes()
{
for (float s = 1.0; s >= 0.1; s -= 0.3) {
drawEyes(currentX, currentY, s);
delay(15);
}
drawEyes(currentX, currentY, 0.05);
delay(40);
for (float s = 0.1; s <= 1.0; s += 0.3) {
drawEyes(currentX, currentY, s);
delay(15);
}
drawEyes(currentX, currentY, 1.0);
}
void setup()
{
Serial.begin(115200);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 OLED baslatilamadi!"));
for (;;);
}
drawEyes(0, 0, 1.0);
}
void loop()
{
// Rastgele bakış pozisyonları
int dir = random(0, 5);
if (dir == 0) moveEyesTo(0, 0, 180);
else if (dir == 1) moveEyesTo(12, 0, 220); // Sağa bak
else if (dir == 2) moveEyesTo(-12, 0, 220); // Sola bak
else if (dir == 3) moveEyesTo(0, -6, 200); // Yukarı bak
else if (dir == 4) moveEyesTo(0, 6, 200); // Aşağı bak
delay(random(1200, 3000));
if (random(0, 10) > 3) {
blinkEyes();
}
}
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <LittleFS.h>
#include "AudioFileSourceLittleFS.h"
#include "AudioGeneratorWAV.h"
#include "AudioOutputI2S.h"
#include <math.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define TOUCH_PIN 4
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
AudioGeneratorWAV *wav = nullptr;
AudioFileSourceLittleFS *file = nullptr;
AudioOutputI2S *out = nullptr;
// =====================================================
// ROBOT DURUMLARI (STATE MACHINE)
// =====================================================
enum RobotState {
STATE_SLEEPING,
STATE_AWAKE,
STATE_DROWSY
};
RobotState robotState = STATE_SLEEPING;
// I2S Çıkışını Başlat (MAX98357A: BCLK=GPIO26, LRC=GPIO25, DIN=GPIO27)
void initAudio()
{
if (!out) {
out = new AudioOutputI2S();
out->SetPinout(26, 25, 27);
out->SetGain(0.4); // 0.4 temiz ve distorsiyonsuz ses sağlar
}
}
// WAV Ses Çal (LittleFS üzerinden)
void playWav(const char *filename)
{
if (!LittleFS.exists(filename)) {
Serial.printf("WAV dosyasi bulunamadi: %s\n", filename);
return;
}
initAudio();
file = new AudioFileSourceLittleFS(filename);
wav = new AudioGeneratorWAV();
if (wav->begin(file, out)) {
while (wav->isRunning()) {
if (!wav->loop()) wav->stop();
yield(); // Kesintisiz DMA akışı ve cızırtı önleme
}
}
delete wav; wav = nullptr;
delete file; file = nullptr;
}
void setup()
{
Serial.begin(115200);
Wire.begin(21, 22);
if (!LittleFS.begin(true)) {
Serial.println("LittleFS baslatilamadi!");
}
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println("SSD1306 bulunamadi!");
}
pinMode(TOUCH_PIN, INPUT);
// Açılış sesi
playWav("/merhaba.wav");
robotState = STATE_AWAKE;
}
void loop()
{
// Dokunmatik kontrol ve durum makinesi döngüsü
int touchState = digitalRead(TOUCH_PIN);
if (touchState == HIGH) {
playWav("/dokunma.wav");
delay(500);
}
}
5 Adımda EMO Robot Yapım ve Kurulum Rehberi
Sıfırdan başlayarak EMO robotunuzu masaüstünüzde canlandırmak için adım adım takip etmeniz gereken aşamalar:
3D Parçaların Yazdırılması ve Çapak Temizliği
Yukarıdaki kafa.stl dosyasını indirip Cura veya Bambu Studio dilimleyicinize atın. 0.20mm katman ve %15 infill ile yazdırın.
Baskı sonrası OLED ekran yuvasını ve hoparlör ızgarasını temizleyin.
Devre Bağlantılarının Yapılması
Pinout tablosuna sadık kalarak OLED ekranı (SDA:21, SCL:22) ve MAX98357A I2S DAC kartını (BCLK:26, LRC:25, DIN:27) ESP32 kartınıza bağlayın. Dokunmatik sensörün sinyal pinini GPIO 4'e takın.
Arduino IDE Kütüphanelerinin Yüklenmesi
Arduino IDE > Araçlar > Kütüphaneleri Yönet menüsünden şu kütüphaneleri aratıp yükleyin:
• Adafruit SSD1306 ve Adafruit GFX Library
• ESP8266Audio (Earle Philhower tarafından geliştirilen kütüphane)
Ses Dosyalarının LittleFS ile Yüklenmesi
İndirdiğiniz WAV seslerini Arduino projenizin içindeki data/ klasörüne yerleştirin.
Arduino IDE menüsünden "ESP32 LittleFS Data Upload" aracına tıklayarak sesleri flash belleğe aktarın.
Montaj, Test ve Çalıştırma
Kodu ESP32'ye yükleyin. Ekran ilk açılışta göz kırpıp merhaba.wav sesini çalacaktır. Kafasına dokunduğunuzda neşeli ses çıkarıp uyanık moda geçtiğini gördüğünüzde EMO'nuz hazır demektir!
Sık Karşılaşılan Sorunlar ve Çözümleri
out->SetGain(0.4); değerini koruyun (0.8 veya 1.0 cızırtı yapar).
İkincisi, ses döngüsünde delay() yerine mutlaka yield(); kullanın; böylece ESP32 DMA ses tamponu hiç boş kalmaz.
0x3C yerine 0x3D olabilir. display.begin(SSD1306_SWITCHCAPVCC, 0x3D) olarak deneyin.
Ayrıca SDA'nın GPIO 21 ve SCL'nin GPIO 22'ye bağlı olduğunu teyit edin.
data/ klasörüne koyduktan sonra Tools > ESP32 Sketch Data Upload işlemi yapmalısınız.
Tüm EMO Proje Paketini İndirin
3D Kafa STL modeli, OLED göz animasyon kodları, tam durum makinesi firmware'i ve WAV ses efektleri tek bir arşivde hazırlandı.