Romeo ESP32-S3 Robot Development Board With The Devastator Tank Mobile Robot Platform

userHead 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);

}