Romeo ESP32-S3 Robot Development Board With The Devastator Tank Mobile Robot Platform
John.Winters 2026-04-01 13:09:07 14 Views0 Replies I am trying to make a web based FPV tank with the Devastator Tank Mobile Robot Platform(R0B0112) and Romeo ESP32-S3 Robot Development Board(DFR0994).
My code works when not streaming video (I comment out line 155). But when I try to stream the control buttons do not work, until I close my browser then the tank tries to runaway. Any ideas?
— Code ----
#include <HTTP_Method.h>
#include <Middlewares.h>
#include <Uri.h>
#include "esp_camera.h"
#include <WiFi.h>
#include <WebServer.h>
#include "DFRobot_AXP313A.h"
// --- Camera Pinout for Romeo S3 (standard OV2640) ---
#define CAMERA_MODEL_DFRobot_Romeo_ESP32S3
#include "camera_pins.h"
DFRobot_AXP313A axp;
const char* ssid = "Devastator_FPV";
const char* password = "password123";
WebServer server(80);
WiFiServer streamServer(81);
// Motor Pins
const int M1_PWM = 12; // Adjust based on your Romeo S3 version
const int M1_DIR = 13;
const int M2_PWM = 14;
const int M2_DIR = 21;
void setup() {
Serial.begin(115200);
while (axp.begin() !=0){
Serial.println("init error");
delay(1000);
}
axp.enableCameraPower(axp.eOV2640);
// 1. Camera Config
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.frame_size = FRAMESIZE_UXGA;
config.pixel_format = PIXFORMAT_JPEG; // for streaming
//config.pixel_format = PIXFORMAT_RGB565; // for face detection/recognition
config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
config.fb_location = CAMERA_FB_IN_PSRAM;
config.jpeg_quality = 32;
config.fb_count = 1;
// if PSRAM IC present, init with UXGA resolution and higher JPEG quality
// for larger pre-allocated frame buffer.
if (config.pixel_format == PIXFORMAT_JPEG) {
if (psramFound()) {
config.jpeg_quality = 10;
config.fb_count = 2;
config.grab_mode = CAMERA_GRAB_LATEST;
} else {
// Limit the frame size when PSRAM is not available
config.frame_size = FRAMESIZE_SVGA;
config.fb_location = CAMERA_FB_IN_DRAM;
}
} else {
// Best option for face detection/recognition
config.frame_size = FRAMESIZE_240X240;
#if CONFIG_IDF_TARGET_ESP32S3
config.fb_count = 2;
#endif
}
#if defined(CAMERA_MODEL_ESP_EYE)
pinMode(13, INPUT_PULLUP);
pinMode(14, INPUT_PULLUP);
#endif
// camera init
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
Serial.printf("Camera init failed with error 0x%x", err);
return;
}
sensor_t *s = esp_camera_sensor_get();
// initial sensors are flipped vertically and colors are a bit saturated
if (s->id.PID == OV3660_PID) {
s->set_vflip(s, 1); // flip it back
s->set_brightness(s, 1); // up the brightness just a bit
s->set_saturation(s, -2); // lower the saturation
}
// drop down frame size for higher initial frame rate
if (config.pixel_format == PIXFORMAT_JPEG) {
s->set_framesize(s, FRAMESIZE_QVGA);
}
// 2. WiFi & Motors
pinMode(M1_PWM, OUTPUT); pinMode(M1_DIR, OUTPUT);
pinMode(M2_PWM, OUTPUT); pinMode(M2_DIR, OUTPUT);
WiFi.softAP(ssid, password);
Serial.println("AP Started. IP: 192.168.4.1");
// 3. Routes
server.on("/", handleRoot);
server.on("/action", handleAction);
server.begin();
streamServer.begin();
}
void loop() {
server.handleClient();
handleStream();
}
// --- Video Streaming Logic ---
void handleStream() {
WiFiClient client = streamServer.available();
if (client) {
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: multipart/x-mixed-replace; boundary=frame");
client.println();
while (client.connected()) {
camera_fb_t * fb = esp_camera_fb_get();
if (!fb) break;
client.printf("--frame\r\nContent-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n", fb->len);
client.write(fb->buf, fb->len);
client.print("\r\n");
esp_camera_fb_return(fb);
}
}
}
// --- Web UI ---
void handleRoot() {
String html = "<html><body style='text-align:center; background:#222; color:white; font-family:sans-serif;'>";
html += "<h1>Devastator FPV</h1>";
// The Image source points to the stream on port 81
//html += "<img src='http://192.168.4.1:81' style='width:320px; border:2px solid orange;'><br><br>";
html += "<H2>";
html += "<button style='width:100px;height:50px' onmousedown=\"fetch('/action?dir=F')\" onmouseup=\"fetch('/action?dir=S')\">FORWARD</button><br>";
html += "<button style='width:100px;height:50px' onmousedown=\"fetch('/action?dir=L')\" onmouseup=\"fetch('/action?dir=S')\">LEFT</button>";
html += "<button style='width:100px;height:50px' onmousedown=\"fetch('/action?dir=R')\" onmouseup=\"fetch('/action?dir=S')\">RIGHT</button><br>";
html += "<button style='width:100px;height:50px' onmousedown=\"fetch('/action?dir=B')\" onmouseup=\"fetch('/action?dir=S')\">BACK</button>";
html += "</H2>";
server.send(200, "text/html", html);
}
void handleAction() {
String dir = server.arg("dir");
if (dir == "F") move(150, 150, 1, 0);
else if (dir == "B") move(150, 150, 0, 1);
else if (dir == "L") move(200, 200, 0, 0);
else if (dir == "R") move(200, 200, 1, 1);
else move(0, 0, 0, 0);
server.send(200);
}
void move(int sL, int sR, bool dL, bool dR) {
digitalWrite(M1_DIR, dL); digitalWrite(M2_DIR, dR);
analogWrite(M1_PWM, sL); analogWrite(M2_PWM, sR);
}

