Maker dünyasında robot araba projeleri bir klasiktir. Çizgi izleyenler, engelden kaçanlar veya Bluetooth ile kontrol edilenler… Ancak ESP32 ile harika projeler serimizin üçüncü bölümünde sıradanlığın dışına çıkıyoruz ve standart bir robot arabayı adeta tekerlekli bir IP kameraya dönüştürüyoruz!

Üçüncü projemizde, oturduğunuz yerden canlı görüntü aktarımı (FPV) alabileceğiniz ve tamamen web tarayıcısı üzerinden komut verebileceğiniz bir ESP32-CAM ile kameralı IoT araba yapıyoruz. Üstelik sadece evdeki modeminize bağlı kalmak zorunda değilsiniz. ESP32-CAM’e cep telefonunuzun Wi-Fi hotspot (taşınabilir ağ) bilgilerini girerek ve aracınızı pillerle besleyerek, onu parkta veya atölyede özgürce gezebilen bir IoT cihazına dönüştüreceksiniz.

Kullanılan Malzemeler
Başarılı bir kameralı IoT araba yapımı için güç yönetimi, motor sistemi ve kablosuz iletişim gibi birden fazla donanımsal yükü aynı anda sırtlayacak malzemelere ihtiyacınız var.

Videoda Kullanılan Kit:
Kitin İçinden Kullanılan Malzemeler:
- ESP32-CAM WiFi Bluetooth Geliştirme Kartı + OV2640 Kamera Modül
- FTDI Programlama Kartı (3.3 V – 5 V Seçilebilir)
- L298N Voltaj Regulatörlü Çift Motor Sürücü Kartı
- REX Chassis Serisi Arduino Çok Amaçlı Mini Platform
- 40 Pin Ayrılabilen Dişi-Erkek F-M Jumper Kablo – 100mm
Kit Haricinde Kullanılan Ek Malzemeler:
Maker İpucu: Güç Kaynağı Seçimi (9V mu, 12V mu?) Projemizi yapım aşamasında olabildiğince pratik ve erişilebilir kılmak adına standart bir 9V pil kullandık. Ancak, ESP32-CAM’in Wi-Fi üzerinden canlı yayın (FPV) yaparken duyduğu yüksek güç ihtiyacı ve L298N motor sürücüsünün iç yapısından kaynaklanan anlık voltaj kayıpları birleştiğinde, standart 9V piller çabuk tükenebilir veya cihazda anlık bağlantı kopmaları (resetlenme) yaşanabilir.

Eğer imkanınız varsa ve IoT araba yapımı projenizden maksimum performans almak istiyorsanız, sistemi 12V bir güç kaynağıyla (örneğin seri bağlı 3 adet 18650 Li-ion pil veya 12V LiPo batarya) beslemenizi şiddetle tavsiye ederiz. 12V kullandığınızda motorlarınız çok daha torklu dönecek, canlı yayınınız akıcı kalacak ve cihazınız çok daha stabil çalışacaktır!
Projenin Öne Çıkan Özellikleri ve Çalışma Mantığı
Projemiz, uzaktan kumandalı basit bir oyuncaktan ziyade ağa bağlı akıllı bir uç cihazdır (Edge Device). ESP32-CAM ile kameralı IoT araba yapımı aşamalarında kullandığımız yazılımsal teknolojiler:
- Gömülü Web Sunucusu: ESP32-CAM, ağa bağlandığında kendi içinde bir HTML sayfası oluşturur. Ekstra mobil uygulamaya gerek kalmadan cihazı Chrome veya Safari’ den kontrol edersiniz.
- Kapanmayan Akış (Stream): Kamera, çektiği JPEG fotoğrafları multipart/x-mixed-replace protokolüyle arayüze basarak akıcı bir canlı yayın sunar.
- Asenkron Kontrol (Fetch API): Arayüzdeki yön butonlarına tıkladığınızda sayfa yenilenmez. fetch() metodu ile komutlar motorlara arka planda iletilir, görüntü kesilmez.

Devre Şemaları ve Bağlantı Adımları
Başarılı bir IoT araba yapımı projesi için kodların kusursuz olması kadar, donanım bağlantılarının da eksiksiz yapılması gerekir. Projede ESP32-CAM kullandığımız için iki aşamalı bir devre kurulumu yapacağız: Önce kod yükleme bağlantısını kuracak, ardından aracın ana şasi bağlantılarına geçeceğiz.
1. Kod Yükleme Aşaması (FTDI Bağlantısı)
ESP32-CAM kartının üzerinde doğrudan bilgisayara bağlayabileceğiniz dahili bir USB portu (Arduino’larda olduğu gibi) bulunmaz. Bu nedenle kodlarımızı yüklemek için bir FTDI programlayıcı kullanıyoruz.
- Bağlantıyı yaparken FTDI üzerindeki TX pinini ESP32’nin U0R (RX) pinine, RX pinini ise U0T (TX) pinine çapraz olarak bağlamanız gerekir.
- ⚠️ En Kritik Maker İpucu: ESP32-CAM’in kod yükleme (flash) moduna geçebilmesi için IO0 (GPIO0) pini ile GND pinini bir jumper kablo ile birbirine bağlamayı (kısa devre yapmayı) kesinlikle unutmayın. Kod yükleme işlemi başarıyla tamamlandıktan sonra bu kabloyu sökerek kartı normal çalışma moduna alabilirsiniz.

2. Ana Devre ve Şasi Bağlantısı
Kodumuzu karta yükledikten sonra aracımızın asıl yürüyen aksamını kuruyoruz.
- Pilimizden gelen gücü doğrudan L298N motor sürücüsünün klemenslerine (12V ve GND) bağlıyoruz.
- ESP32-CAM’in çalışması için gereken 5V gücü ekstra bir regülatör kullanmadan, doğrudan L298N motor sürücüsünün üzerindeki “5V” çıkışından alarak ESP32’nin 5V pinine giriyoruz. Bu, devremizi hem kablo kalabalığından kurtarır hem de çok daha kompakt hale getirir.
- Son olarak tekerlek yönlerini belirlemek için L298N üzerindeki IN1, IN2, IN3 ve IN4 kontrol pinlerini, devrede göründüğü gibi ESP32-CAM’in kodda belirttiğimiz (12, 13, 14 ve 15. pinler) GPIO pinlerine bağlıyoruz.

Proje Kodları
Projemizi hayata geçirmek için aşağıdaki kodları FTDI kartı ile yükleyebilirsiniz. Sistemin arka planda nasıl çalıştığını tam olarak kavrayabilmeniz için kodlama kısmını iki aşamada ele alıyoruz:
Temel Kod (Klasik Yöntem)
Aşağıdaki temel kod, cihazımızı yerel ağa bağlar ve oluşturduğu web sayfası üzerinden hareket etmesini sağlar. Ancak bu kodda eski usul HTML buton yönlendirmeleri (location.href) kullanıldığı için, her “İleri” veya “Geri” komutu verdiğinizde tarayıcı sayfayı yeniler. Bu da kamera yayınında (stream) can sıkıcı donmalara ve anlık kopmalara neden olur.
#include "esp_camera.h"
#include <WiFi.h>
#include "esp_http_server.h"
// Wi-Fi bilgileri
const char* ssid = "";
const char* password = "";
// L298N pinleri
#define IN1 12 // Sol motor
#define IN2 13
#define IN3 14 // Sağ motor
#define IN4 15
// Kamera pinleri (AI-Thinker ESP32-CAM)
#define PWDN_GPIO_NUM 32
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 0
#define SIOD_GPIO_NUM 26
#define SIOC_GPIO_NUM 27
#define Y9_GPIO_NUM 35
#define Y8_GPIO_NUM 34
#define Y7_GPIO_NUM 39
#define Y6_GPIO_NUM 36
#define Y5_GPIO_NUM 21
#define Y4_GPIO_NUM 19
#define Y3_GPIO_NUM 18
#define Y2_GPIO_NUM 5
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 23
#define PCLK_GPIO_NUM 22
void stopMotors() {
digitalWrite(IN1, LOW);
digitalWrite(IN2, LOW);
digitalWrite(IN3, LOW);
digitalWrite(IN4, LOW);
}
// Kamera stream handler
static esp_err_t stream_handler(httpd_req_t *req){
camera_fb_t * fb = NULL;
esp_err_t res = ESP_OK;
char buf[64];
res = httpd_resp_set_type(req, "multipart/x-mixed-replace; boundary=frame");
if(res != ESP_OK) return res;
while(true){
fb = esp_camera_fb_get();
if(!fb){
res = ESP_FAIL;
break;
}
sprintf(buf, "--frame\r\nContent-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n", fb->len);
res = httpd_resp_send_chunk(req, buf, strlen(buf));
if(res == ESP_OK) res = httpd_resp_send_chunk(req, (const char *)fb->buf, fb->len);
if(res == ESP_OK) res = httpd_resp_send_chunk(req, "\r\n", 2);
esp_camera_fb_return(fb);
if(res != ESP_OK) break;
}
return res;
}
// Ana web sayfası handler
static esp_err_t index_handler(httpd_req_t *req){
String html = "<!DOCTYPE html><html><head><meta charset='UTF-8'><title>ESP32-CAM Robot</title></head><body>";
html += "<h1>ESP32-CAM Robot Kontrol</h1>";
html += "<button style='display:block;margin:5px;width:150px;' onclick=\"location.href='/ileri'\">İleri</button>";
html += "<button style='display:block;margin:5px;width:150px;' onclick=\"location.href='/geri'\">Geri</button>";
html += "<button style='display:block;margin:5px;width:150px;' onclick=\"location.href='/saga'\">Sağ (sağ motor)</button>";
html += "<button style='display:block;margin:5px;width:150px;' onclick=\"location.href='/sola'\">Sol (sol motor)</button>";
html += "<button style='display:block;margin:5px;width:150px;' onclick=\"location.href='/dur'\">Dur</button>";
html += "<h2>Canlı Kamera</h2>";
html += "<img src='/stream' style='width:320px;height:240px;'>";
html += "</body></html>";
httpd_resp_set_type(req, "text/html; charset=UTF-8");
httpd_resp_send(req, html.c_str(), html.length());
return ESP_OK;
}
// Motor kontrol handlerları
static esp_err_t ileri_handler(httpd_req_t *req){ digitalWrite(IN1,HIGH); digitalWrite(IN2,LOW); digitalWrite(IN3,HIGH); digitalWrite(IN4,LOW); return ESP_OK; }
static esp_err_t geri_handler(httpd_req_t *req){ digitalWrite(IN1,LOW); digitalWrite(IN2,HIGH); digitalWrite(IN3,LOW); digitalWrite(IN4,HIGH); return ESP_OK; }
static esp_err_t saga_handler(httpd_req_t *req){ digitalWrite(IN1,LOW); digitalWrite(IN2,LOW); digitalWrite(IN3,HIGH); digitalWrite(IN4,LOW); return ESP_OK; }
static esp_err_t sola_handler(httpd_req_t *req){ digitalWrite(IN1,HIGH); digitalWrite(IN2,LOW); digitalWrite(IN3,LOW); digitalWrite(IN4,LOW); return ESP_OK; }
static esp_err_t dur_handler(httpd_req_t *req){ stopMotors(); return ESP_OK; }
void startCameraServer(){
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
httpd_handle_t server = NULL;
if(httpd_start(&server,&config) == ESP_OK){
// Ana sayfa
httpd_uri_t index_uri = {"/", HTTP_GET, index_handler, NULL};
httpd_register_uri_handler(server, &index_uri);
// Stream
httpd_uri_t stream_uri = {"/stream", HTTP_GET, stream_handler, NULL};
httpd_register_uri_handler(server, &stream_uri);
// Motor komutları
httpd_uri_t ileri_uri = {"/ileri", HTTP_GET, ileri_handler, NULL};
httpd_register_uri_handler(server, &ileri_uri);
httpd_uri_t geri_uri = {"/geri", HTTP_GET, geri_handler, NULL};
httpd_register_uri_handler(server, &geri_uri);
httpd_uri_t saga_uri = {"/saga", HTTP_GET, saga_handler, NULL};
httpd_register_uri_handler(server, &saga_uri);
httpd_uri_t sola_uri = {"/sola", HTTP_GET, sola_handler, NULL};
httpd_register_uri_handler(server, &sola_uri);
httpd_uri_t dur_uri = {"/dur", HTTP_GET, dur_handler, NULL};
httpd_register_uri_handler(server, &dur_uri);
}
}
void setup() {
Serial.begin(115200);
// Motor pinleri
pinMode(IN1, OUTPUT); pinMode(IN2, OUTPUT);
pinMode(IN3, OUTPUT); pinMode(IN4, OUTPUT);
stopMotors();
// WiFi bağlan
WiFi.begin(ssid,password);
Serial.print("WiFi bağlanıyor...");
while(WiFi.status()!=WL_CONNECTED){delay(500);Serial.print(".");}
Serial.println(""); Serial.print("Bağlandı! IP: "); Serial.println(WiFi.localIP());
// Kamera ayarları
camera_config_t config;
config.ledc_channel = LEDC_CHANNEL_0;
config.ledc_timer = LEDC_TIMER_0;
config.pin_d0 = Y2_GPIO_NUM; config.pin_d1 = Y3_GPIO_NUM; config.pin_d2 = Y4_GPIO_NUM; config.pin_d3 = Y5_GPIO_NUM;
config.pin_d4 = Y6_GPIO_NUM; config.pin_d5 = Y7_GPIO_NUM; config.pin_d6 = Y8_GPIO_NUM; config.pin_d7 = Y9_GPIO_NUM;
config.pin_xclk = XCLK_GPIO_NUM; config.pin_pclk = PCLK_GPIO_NUM; config.pin_vsync = VSYNC_GPIO_NUM; config.pin_href = HREF_GPIO_NUM;
config.pin_sccb_sda = SIOD_GPIO_NUM; config.pin_sccb_scl = SIOC_GPIO_NUM;
config.pin_pwdn = PWDN_GPIO_NUM; config.pin_reset = RESET_GPIO_NUM;
config.xclk_freq_hz = 20000000;
config.pixel_format = PIXFORMAT_JPEG;
config.frame_size = FRAMESIZE_QVGA;
config.jpeg_quality = 12;
config.fb_count = 1;
esp_camera_init(&config);
startCameraServer();
Serial.println("Kamera sunucusu ve web arayüzü hazır.");
}
void loop() {
// Her şey HTTP server üzerinden çalışıyor
}
Geliştirilmiş Kod (Fetch API ve Kesintisiz Yayın)
İlk koddaki donma sorununu çözmek için JavaScript’in modern fetch() metodunu kullanıyoruz. Bu sayede cihazımıza giden hareket komutları arka planda gizlice iletiliyor. Ayrıca kamera ayarlarında fb_count = 2 yaparak kamerayı hızlandırıyor ve “çift tamponlama” sağlıyoruz.
Sayfa yenilenme derdi bitiyor ve canlı yayınımız sürüş esnasında kesilmiyor! Cihazınızı en yüksek performansta kullanmak için karta bu gelişmiş versiyonu yüklemenizi şiddetle tavsiye ederiz:
(Kodu yüklemeden önce ssid ve password kısımlarına ağ bilgilerinizi girmeyi unutmayın!)
#include "esp_camera.h"
#include <WiFi.h>
#include "esp_http_server.h"
// WiFi
const char* ssid = "";
const char* password = "";
// L298N
#define IN1 12
#define IN2 13
#define IN3 14
#define IN4 15
// AI Thinker kamera pinleri
#define PWDN_GPIO_NUM 32
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 0
#define SIOD_GPIO_NUM 26
#define SIOC_GPIO_NUM 27
#define Y9_GPIO_NUM 35
#define Y8_GPIO_NUM 34
#define Y7_GPIO_NUM 39
#define Y6_GPIO_NUM 36
#define Y5_GPIO_NUM 21
#define Y4_GPIO_NUM 19
#define Y3_GPIO_NUM 18
#define Y2_GPIO_NUM 5
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 23
#define PCLK_GPIO_NUM 22
void stopMotors(){
digitalWrite(IN1,LOW);
digitalWrite(IN2,LOW);
digitalWrite(IN3,LOW);
digitalWrite(IN4,LOW);
}
//////////////////////////////////////////
// Kamera Stream
//////////////////////////////////////////
static esp_err_t stream_handler(httpd_req_t *req){
camera_fb_t * fb = NULL;
esp_err_t res = ESP_OK;
char part_buf[64];
res = httpd_resp_set_type(req, "multipart/x-mixed-replace; boundary=frame");
while(true){
fb = esp_camera_fb_get();
if (!fb){
Serial.println("Frame alınamadı");
return ESP_FAIL;
}
size_t hlen = snprintf(part_buf, 64,
"--frame\r\nContent-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n",
fb->len);
res = httpd_resp_send_chunk(req, part_buf, hlen);
if(res == ESP_OK){
res = httpd_resp_send_chunk(req, (const char *)fb->buf, fb->len);
}
if(res == ESP_OK){
res = httpd_resp_send_chunk(req, "\r\n", 2);
}
esp_camera_fb_return(fb);
if(res != ESP_OK){
break;
}
}
return res;
}
//////////////////////////////////////////
// Web Sayfası
//////////////////////////////////////////
static esp_err_t index_handler(httpd_req_t *req){
String html = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ESP32 Robot</title>
<style>
button{
width:120px;
height:40px;
margin:5px;
font-size:16px;
}
</style>
</head>
<body>
<h2>Robot Kontrol</h2>
<button onclick="fetch('/ileri')">İleri</button><br>
<button onclick="fetch('/geri')">Geri</button><br>
<button onclick="fetch('/saga')">Sağ</button><br>
<button onclick="fetch('/sola')">Sol</button><br>
<button onclick="fetch('/dur')">Dur</button>
<h2>Kamera</h2>
<img src="/stream" width="320">
</body>
</html>
)rawliteral";
httpd_resp_set_type(req, "text/html");
httpd_resp_send(req, html.c_str(), html.length());
return ESP_OK;
}
//////////////////////////////////////////
// Motor handlerları
//////////////////////////////////////////
static esp_err_t ileri_handler(httpd_req_t *req){
digitalWrite(IN1,HIGH);
digitalWrite(IN2,LOW);
digitalWrite(IN3,HIGH);
digitalWrite(IN4,LOW);
httpd_resp_send(req,NULL,0);
return ESP_OK;
}
static esp_err_t geri_handler(httpd_req_t *req){
digitalWrite(IN1,LOW);
digitalWrite(IN2,HIGH);
digitalWrite(IN3,LOW);
digitalWrite(IN4,HIGH);
httpd_resp_send(req,NULL,0);
return ESP_OK;
}
static esp_err_t saga_handler(httpd_req_t *req){
digitalWrite(IN1,LOW);
digitalWrite(IN2,LOW);
digitalWrite(IN3,HIGH);
digitalWrite(IN4,LOW);
httpd_resp_send(req,NULL,0);
return ESP_OK;
}
static esp_err_t sola_handler(httpd_req_t *req){
digitalWrite(IN1,HIGH);
digitalWrite(IN2,LOW);
digitalWrite(IN3,LOW);
digitalWrite(IN4,LOW);
httpd_resp_send(req,NULL,0);
return ESP_OK;
}
static esp_err_t dur_handler(httpd_req_t *req){
stopMotors();
httpd_resp_send(req,NULL,0);
return ESP_OK;
}
//////////////////////////////////////////
// Server
//////////////////////////////////////////
void startCameraServer(){
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
httpd_handle_t server = NULL;
if(httpd_start(&server,&config)==ESP_OK){
httpd_uri_t index_uri = {"/",HTTP_GET,index_handler,NULL};
httpd_register_uri_handler(server,&index_uri);
httpd_uri_t stream_uri = {"/stream",HTTP_GET,stream_handler,NULL};
httpd_register_uri_handler(server,&stream_uri);
httpd_uri_t ileri_uri = {"/ileri",HTTP_GET,ileri_handler,NULL};
httpd_register_uri_handler(server,&ileri_uri);
httpd_uri_t geri_uri = {"/geri",HTTP_GET,geri_handler,NULL};
httpd_register_uri_handler(server,&geri_uri);
httpd_uri_t saga_uri = {"/saga",HTTP_GET,saga_handler,NULL};
httpd_register_uri_handler(server,&saga_uri);
httpd_uri_t sola_uri = {"/sola",HTTP_GET,sola_handler,NULL};
httpd_register_uri_handler(server,&sola_uri);
httpd_uri_t dur_uri = {"/dur",HTTP_GET,dur_handler,NULL};
httpd_register_uri_handler(server,&dur_uri);
}
}
//////////////////////////////////////////
// Setup
//////////////////////////////////////////
void setup(){
Serial.begin(115200);
pinMode(IN1,OUTPUT);
pinMode(IN2,OUTPUT);
pinMode(IN3,OUTPUT);
pinMode(IN4,OUTPUT);
stopMotors();
WiFi.begin(ssid,password);
Serial.print("WiFi bağlanıyor");
while(WiFi.status()!=WL_CONNECTED){
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.print("IP adresi: ");
Serial.println(WiFi.localIP());
camera_config_t config;
config.ledc_channel = LEDC_CHANNEL_0;
config.ledc_timer = LEDC_TIMER_0;
config.pin_d0 = Y2_GPIO_NUM;
config.pin_d1 = Y3_GPIO_NUM;
config.pin_d2 = Y4_GPIO_NUM;
config.pin_d3 = Y5_GPIO_NUM;
config.pin_d4 = Y6_GPIO_NUM;
config.pin_d5 = Y7_GPIO_NUM;
config.pin_d6 = Y8_GPIO_NUM;
config.pin_d7 = Y9_GPIO_NUM;
config.pin_xclk = XCLK_GPIO_NUM;
config.pin_pclk = PCLK_GPIO_NUM;
config.pin_vsync = VSYNC_GPIO_NUM;
config.pin_href = HREF_GPIO_NUM;
config.pin_sccb_sda = SIOD_GPIO_NUM;
config.pin_sccb_scl = SIOC_GPIO_NUM;
config.pin_pwdn = PWDN_GPIO_NUM;
config.pin_reset = RESET_GPIO_NUM;
config.xclk_freq_hz = 20000000;
config.pixel_format = PIXFORMAT_JPEG;
config.frame_size = FRAMESIZE_QVGA;
config.jpeg_quality = 12;
config.fb_count = 2;
esp_err_t err = esp_camera_init(&config);
if(err != ESP_OK){
Serial.println("Kamera başlatılamadı");
return;
}
startCameraServer();
Serial.println("Sistem hazır");
}
void loop(){}
🎬 Videolu Anlatım
Tüm aşamaları görsel olarak takip etmek ve ESP32-CAM ile kameralı IoT araba yapımı sürecini adım adım izlemek isterseniz, proje serimizin üçüncü bölümü için hazırladığımız YouTube videomuza hemen aşağıdan göz atabilirsiniz. İyi seyirler!
Siz Bu Projeyi Nasıl Geliştirirdiniz? Yorumlarda Buluşalım!
Eğitici ve oldukça eğlenceli olan ESP32-CAM ile kameralı IoT araba yapımı projesiyle, IoT dünyasına kelimenin tam anlamıyla tekerlek takmış olduk. Siz bu cihaza ne gibi özellikler eklerdiniz? Gece sürüşleri için beyaz bir LED modülü mü, yoksa etrafı daha rahat inceleyebilmek için kameraya bir “Pan-Tilt” mekanizması mı? Fikirlerinizi, donanımla ilgili sorularınızı ve geliştirmelerinizi mutlaka aşağıdaki yorumlar kısmında paylaşın. Yeni projelerde görüşmek üzere!







