Video camera | esp32 cam + servo

In short, I want to make a video camera (I already tried to use ready-made solutions, but they did not fit) with an esp32 cam board and two servos. My main problem is that when using the camera and servo libraries in the code, only the first one works correctly, and the servos twitch when writing () or do not react at all. But if you use only servos in the code, then they work fine. To power the circuit, I use one 18650 and a TP4056 charging board set to 5 volts. Here is my main code:

#include <WebServer.h>
#include <WiFi.h>
#include <ESP32Servo.h>
#include "esp_camera.h"

const char* ssid = "Redmi";
const char* password = "11111111";

WebServer server(80);
Servo sX, sY;

const char index_html[] PROGMEM = R"rawliteral(
<body>
  <img id="img">
  <br>
  <button onclick="camUp()">capture</button>
  <br>
  <label id="lv">90</label>
  <br>
  <input type="range" onchange="servo()" id="v" min="0" max="180" step="1" value="90">
  <br>
  <label id="lh">90</label>
  <br>
  <input type="range" onchange="servo()" id="h" min="0" max="180" step="1" value="90">
  <script>
    function camUp() {
      img = document.getElementById("img")
      img.src = "/capture?" + new Date().getTime()
    }
    function servo() {
      let xhr = new XMLHttpRequest()
      xhr.open("GET", "/servo?v="+v.value+"&h="+h.value, false)
      xhr.send()
    }
    window.onload = () => {
      camUp()
      h = document.getElementById("h")
      v = document.getElementById("v")

      lv = document.getElementById("lv")
      lh = document.getElementById("lh")

      v.addEventListener("input", (event) => {
        lv.innerHTML = event.target.value
      })
      h.addEventListener("input", (event) => {
        lh.innerHTML = event.target.value
      })
    }
  </script>
</body>
)rawliteral";

void setup() {
  Serial.begin(115200);
  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = 5;
  config.pin_d1 = 18;
  config.pin_d2 = 19;
  config.pin_d3 = 21;
  config.pin_d4 = 36;
  config.pin_d5 = 39;
  config.pin_d6 = 34;
  config.pin_d7 = 35;
  config.pin_xclk = 0;
  config.pin_pclk = 22;
  config.pin_vsync = 25;
  config.pin_href = 23;
  config.pin_sscb_sda = 26;
  config.pin_sscb_scl = 27;
  config.pin_pwdn = 32;
  config.pin_reset = -1;
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_JPEG;
  config.frame_size = FRAMESIZE_QVGA;
  config.jpeg_quality = 10;
  config.fb_count = 1;

  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    return;
  }

  sX.attach(14);
  sY.attach(15);

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi...");
  }
  Serial.println("Connected to WiFi");

  Serial.println(WiFi.localIP());

  server.on("/", root);
  server.on("/capture", capture);
  server.on("/servo", servo);
  server.begin();
  Serial.println("Server started");
}

void loop() {
  server.handleClient();
}

void root() {
  Serial.println("Root");
  server.send(200, "text/html", index_html);
}

void capture() {
  Serial.println("Capture");
  
  camera_fb_t * fb = esp_camera_fb_get();
  esp_camera_fb_return(fb);
  fb = NULL;
  fb = esp_camera_fb_get();
  if (!fb) {
    Serial.println("Camera capture failed");
    return;
  }

  server.sendHeader("Content-Type", "image/jpeg");  
  server.sendContent((const char *)fb->buf, fb->len);
    
  esp_camera_fb_return(fb);
}

void servo() {
  if (server.args()>0){
    delay(500);
    sX.write(server.arg("v").toInt());
    sY.write(server.arg("h").toInt());
  } 
  delay(500);
}

shouldn't config be defined as a global?

shouldn't the code make sure each argument is valid?

Welcome to the forum.

You have come across the problem that not all things in libraries are compatible with other libraries.

The servo library works by using a timer to generate an interrupt to create the servo pulses you need. This needs to be done at a constant rate otherwise they twitch, or as you have found, don't respond.

So what must be happening is that the camera library is taking great chunks of time with the interrupts disabled while the camera is being dealt with. This is disrupting the generation of the servo pulses.

The simple solution is to use an external device to generate the servo pulses. Something like this:-

The more complex solution, at least for me, is that the esp32 has two CPU cores. So you could use a the second core to generate the servo signals. But I can't tell you how to do this because I never play with a processor's other cores.

The 'camera_config_t' type is just a convenient way of passing lots of arguments to esp_camera_init(). It's no longer needed after that function call.

The ESP32CAM and the ESP32 servo library uses the ESP32's LEDc API. The ESP32Servo API assigns its timers in the following sequence 0,1,2,3. The ESP32 CAM assigns LEDc timers in the following sequence 0,1,2,3. As one may be able to see that both the ESP32cam and the ESP32servo library can claim at the same time timer 0 config.ledc_timer = LEDC_TIMER_0;, which would cause a conflict. Perhaps configuring the ESP32 Cam to use timer 3 instead of timer 0 could work.

Next the ESP32cam and servo's are power hungry,

1 18650 battery may not have enough oomph to drive the ESP32 cam and the servos at the same time. A typical plastic geared servo requires 800mA to 1A of available amperage per servo. Round that off the 1 amp per servo. A typical off the shelf 18650 is typically rated at 1C or .5C. 1C is barely enough available current to drive both a plastic geared servo and the ESP32cam at the same time.

Drawing more then 1C or .5C from a off the shelf LiPo can cause a fire.

If the ESP32cam is using WiFi then core0 is being used for WiFi.

So can't core 1 be used to generate the servo pulses?

Neither core 0 nor core 1 generates servo pulses when using the LEDc or MCPWM API.
My prefrence to generate PWM with the ESP32 is to use the MCPWM API over the LEDc API. The ESP32's internal timers or the MCPWM can generate servo pulses. The ESP32 does not require CPU time to generate PWM.

I tried it, unfortunately it didn't work :frowning:

Not enough 1 18650 battery sounds good, but the battery spec (I'm using Samsung 30Q) says enough current output, maybe the problem is with the tp4056 board I use to power the circuit and MG90s servos.

By the way, I studied what MCPWM is and I was very interested in it, but I don't understand how to change my code to use MCPWM (sorry, maybe it's pretty easy, but I really don't know what to do, sorry again). Can you help?

I can give you some code I am using to drive servo's with the MCPWM if you'd like but I am not going to detail how it works.

A tp4056 is good for an amp.

1 Like
/*
   Project, use solar cells to generate power
   2/2/2020

*/
// https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/system/esp_timer.html
///esp_timer_get_time(void) //gets time in uSeconds like Arduino Micros. see above link
//////// http://www.iotsharing.com/2017/09/how-to-use-arduino-esp32-can-interface.html
#include "sdkconfig.h"
#include "esp_system.h" //This inclusion configures the peripherals in the ESP system.
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "freertos/event_groups.h"
// #include "esp32/ulp.h"
// #include "driver/rtc_io.h"
#include <SimpleKalmanFilter.h>
#include <driver/adc.h>
#include "esp_sleep.h"
#include "driver/mcpwm.h"
const int TaskCore1 = 1;
const int TaskCore0 = 0;
const int TaskStack20K = 20000;
const int Priority3 = 3;
const int Priority4 = 4;
const int SerialDataBits = 115200;
volatile bool EnableTracking = true;
////
//
////
void setup()
{
  Serial.begin( 115200 );
  // https://dl.espressif.com/doc/esp-idf/latest/api-reference/peripherals/adc.html
  // set up A:D channels
  log_i( "Setup A:D" );
  adc_power_on( );
  vTaskDelay( 1 );
  adc1_config_width(ADC_WIDTH_12Bit);
  // ADC1 channel 0 is GPIO36 (ESP32), GPIO1 (ESP32-S2)
  adc1_config_channel_atten(ADC1_CHANNEL_0 , ADC_ATTEN_DB_11); // azimuth 0
  //  // ADC1_CHANNEL_3  ADC1 channel 3 is GPIO39 (ESP32)
  adc1_config_channel_atten(ADC1_CHANNEL_3, ADC_ATTEN_DB_11); // azimuth 1
  //  // ADC1 channel 5 is GPIO33
  adc1_config_channel_atten(ADC1_CHANNEL_5, ADC_ATTEN_DB_11); // altitude 1
  //  // ADC1 channel 6 is GPIO34
  adc1_config_channel_atten(ADC1_CHANNEL_6, ADC_ATTEN_DB_11); // altitude 0
  //  // adc for light dark detection, ADC1 channel 7 is GPIO35 (ESP32)
  adc1_config_channel_atten(ADC1_CHANNEL_7, ADC_ATTEN_DB_11);
  // adc for solar cell voltage detection, ADC1 channel 4 is GPIO33 (ESP32)
  adc1_config_channel_atten(ADC1_CHANNEL_4, ADC_ATTEN_DB_11);
  //
  log_i( "A:D setup complete" );
  // control for relay to supply power to the servos
  pinMode( 5, OUTPUT );
  vTaskDelay(1);
  log_i( "setup MCPWM" );
  //
  mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM1A, GPIO_NUM_4 ); // Azimuth
  mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, GPIO_NUM_12 ); // Altitude servo
  //
  mcpwm_config_t pwm_config = {};
  pwm_config.frequency = 50;    //frequency = 50Hz, i.e. for every servo motor time period should be 20ms
  pwm_config.cmpr_a = 0;    //duty cycle of PWMxA = 0
  pwm_config.cmpr_b = 0;    //duty cycle of PWMxb = 0
  pwm_config.counter_mode = MCPWM_UP_COUNTER;
  pwm_config.duty_mode = MCPWM_DUTY_MODE_0;
  log_i( "MCPWM complete" );
  //
  //
  mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &pwm_config);    //Configure PWM0A timer 0 with above settings
  mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_1, &pwm_config);    //Configure PWM0A timer 1 with above settings
  //  ////
  fMoveAltitude( 1525 );
  fMoveAzimuth( 1500 );
  ////
  xTaskCreatePinnedToCore( TrackSun, "TrackSun", TaskStack20K, NULL, Priority3, NULL, TaskCore1 ); // assigned to core
  xTaskCreatePinnedToCore( fDaylight, "fDaylight", TaskStack20K, NULL, Priority3, NULL, TaskCore0 ); // assigned to core
  ////  // esp_sleep_enable_timer_wakeup( (60000000 * 2) ); // set timer to wake up once a minute 60000000uS * 2

}
//
void fDaylight( void * pvParameters )
{
  int lightLevel = 0;
  log_i( "Start up fDaylight" );
  while (1)
  {
    // sufficent voltage at the solar cells to do the thing?
    log_i( "Solar Cell: %d", adc1_get_raw(ADC1_CHANNEL_4) );
    if (adc1_get_raw(ADC1_CHANNEL_4) > 1600 )
    {
      lightLevel = adc1_get_raw(ADC1_CHANNEL_7);
      if ( adc1_get_raw(ADC1_CHANNEL_7) >= 300 )
      {
        log_i( "Light Level: %d", lightLevel );
        EnableTracking = true;
        digitalWrite( 5, LOW ); // power to relay module/servos
        vTaskDelay( 1 );
      } else {
        // if light level to low park boom
        fMoveAltitude( 1475 );
        fMoveAzimuth( 1900 );
        //     put esp32 into deep sleep
        digitalWrite( 5, HIGH ); // deenergize servo motors
        EnableTracking = false;
        esp_sleep_enable_timer_wakeup( (60000000 * 5) ); // set timer to wake up once a minute 60000000uS
        esp_deep_sleep_start();
      }
    } else {
              digitalWrite( 5, HIGH ); // deenergize servo motors
        EnableTracking = false;
        esp_sleep_enable_timer_wakeup( (60000000 * 5) ); // set timer to wake up once a minute 60000000uS
        esp_deep_sleep_start();
    }
    vTaskDelay( 1000 );
  } // while(1)
} // void fDaylight( void * pvParameters )
////
/**
   @brief Use this function to calcute pulse width for per degree rotation
   @param  degree_of_rotation the angle in degree to which servo has to rotate
   @return
       - calculated pulse width
*/
//static uint32_t servo_per_degree_init(uint32_t degree_of_rotation)
//{
//  const int SERVO_MIN_PULSEWIDTH = 500; //Minimum pulse width in microsecond
//const int SERVO_MAX_PULSEWIDTH 2500 //Maximum pulse width in microsecond
//const int SERVO_MAX_DEGREE 180 //Maximum angle in degree upto which servo can rotate
//  uint32_t cal_pulsewidth = 0;
//  cal_pulsewidth = (SERVO_MIN_PULSEWIDTH + (((SERVO_MAX_PULSEWIDTH - SERVO_MIN_PULSEWIDTH) * (degree_of_rotation)) / (SERVO_MAX_DEGREE)));
//  return cal_pulsewidth;
//}
////
void mcpwm_gpio_initialize(void)
{
  mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM1A, GPIO_NUM_4 );
  mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, GPIO_NUM_12 );
}
////
// void TrackSun( int Altitude, int Azimuth )
void TrackSun( void * pvParameters )
{
  log_i( "Startup TrackSun" );
  int64_t EndTime = esp_timer_get_time();
  int64_t StartTime = esp_timer_get_time(); //gets time in uSeconds like Arduino Micros
  int Altitude = 1500;
  int Azimuth = 900;
  int maxAltitudeRange = 2144;
  int minAltitudeRange = 900;
  int maxAzimuthRange = 2144;
  int minAzimuthRange = 900;
  float AltitudeThreashold = 10.0f;
  float AzimuthThreashold = 10.0f;
  float kalmanThreshold = 80.0f;
  SimpleKalmanFilter kfAltitude0( AltitudeThreshold, kalmanThreshold, .01 ); // kalman filter Altitude 0
  SimpleKalmanFilter kfAltitude1( AltitudeThreshold, kalmanThreshold, .01 ); // kalman filter Altitude 1
  SimpleKalmanFilter kfAzimuth0( AzimuthThreshold, kalmanThreshold, .01 ); // kalman filter Azimuth 0
  SimpleKalmanFilter kfAzimuth1( AzimuthThreshold, kalmanThreshold, .01 ); // kalman filter Azimuth 1
  float filteredAltitude_0 = 0.0f;
  float filteredAltitude_1 = 0.0f;
  float filteredAzimuth_0 = 0.0f;
  float filteredAzimuth_1 = 0.0f;
  uint64_t AzimuthEndTime = esp_timer_get_time();
  uint64_t AzimuthStartTime = esp_timer_get_time(); //gets time in uSeconds like Arduino Micros,but rolls over after 207 years
  uint64_t AltitudeEndTime = esp_timer_get_time();
  uint64_t AltitudeStartTime = esp_timer_get_time(); //gets time in uSeconds like Arduino Micros,
  int StartupCount = 0;
  int TorqueAmmount = 5;
  while (1)
  {

    if ( EnableTracking )
    {
      //Altitude
      AltitudeEndTime = esp_timer_get_time() - AltitudeStartTime; // produce elapsed time for the simpleKalmanFilter
      kfAltitude0.setProcessNoise( float(AltitudeEndTime) / 1000000.0f );
      kfAltitude1.setProcessNoise( float(AltitudeEndTime) / 1000000.0f );
      // Serial.println( AltitudeEndTime,6 );
      filteredAltitude_0 = kfAltitude0.updateEstimate( (float)adc1_get_raw(ADC1_CHANNEL_6) );
      filteredAltitude_1 = kfAltitude1.updateEstimate( (float)adc1_get_raw(ADC1_CHANNEL_5) );
      //      Serial.print( filteredAltitude_0 );
      //      Serial.print( ", " );
      //      Serial.print( filteredAltitude_1 );
      //      Serial.print( ", " );
      //      Serial.println();
      if ( (filteredAltitude_0 > filteredAltitude_1) && (abs(filteredAltitude_0 - filteredAltitude_1) > AltitudeThreshold))
      {
        Altitude -= TorqueAmmount;
        // log_i( "> Alt %d", Altitude );
        if ( Altitude < minAltitudeRange )
        {
          Altitude = 900;
        }
        fMoveAltitude( Altitude );
        AltitudeStartTime = esp_timer_get_time();
      }
      if ( (filteredAltitude_0 < filteredAltitude_1) && (abs(filteredAltitude_0 - filteredAltitude_1) > AltitudeThreshold) )
      {
        Altitude += TorqueAmmount;
        if ( Altitude >= maxAltitudeRange )
        {
          Altitude = 900;
        }
        fMoveAltitude( Altitude );
        // log_i( "< Alt %d", Altitude );
        AltitudeStartTime = esp_timer_get_time();
      }
      //// AZIMUTH
      AzimuthEndTime = esp_timer_get_time() - AzimuthStartTime; // produce elasped time for the simpleKalmanFilter
      //      Serial.print( (float)adc1_get_raw(ADC1_CHANNEL_3) );
      //      Serial.print( "," );
      //      Serial.print( (float)adc1_get_raw(ADC1_CHANNEL_0) );
      //      Serial.print( "," );
      kfAzimuth0.setProcessNoise( float(AzimuthEndTime) / 1000000.0f ); //convert time of process to uS, update SimpleKalmanFilter q
      kfAzimuth1.setProcessNoise( float(AzimuthEndTime) / 1000000.0f ); //convert time of process to uS, update SimpleKalmanFilter q
      filteredAzimuth_0 = kfAzimuth0.updateEstimate( (float)adc1_get_raw(ADC1_CHANNEL_3) );
      filteredAzimuth_1 = kfAzimuth1.updateEstimate( (float)adc1_get_raw(ADC1_CHANNEL_0) );
      //      Serial.print( filteredAzimuth_0 );
      //      Serial.print( ", " );
      //      Serial.print( filteredAzimuth_1 );
      //      Serial.println();
      if ( (filteredAzimuth_0 > filteredAzimuth_1) && (abs(filteredAzimuth_0 - filteredAzimuth_1)) > AzimuthThreashold )
      {
        Azimuth += TorqueAmmount;
        if ( Azimuth >= maxAzimuthRange )
        {
          Azimuth = 900;
        }
        // log_i( "> Az %d", Azimuth );
        fMoveAzimuth( Azimuth );
        AzimuthStartTime = esp_timer_get_time();
      }
      //    //
      if ( (filteredAzimuth_0 < filteredAzimuth_1) && (abs(filteredAzimuth_1 - filteredAzimuth_0)) > AzimuthThreashold )
      {
        // log_i( "< Az %d", Azimuth );
        Azimuth -= TorqueAmmount; // make new position to torque to
        if ( (Azimuth >= maxAzimuthRange) || (Azimuth <= minAzimuthRange) )
        {
          Azimuth = 900;
        }
        fMoveAzimuth( Azimuth );
        AzimuthStartTime = esp_timer_get_time();
      } //azmiuth end
      if ( StartupCount < 500 )
      {
        ++StartupCount;
      } else {
        TorqueAmmount = 1;
        // esp_sleep_enable_timer_wakeup( (60000000 / 30) ); // set timer to wake up
        // esp_sleep_enable_timer_wakeup( (60000000 / 20) ); // set timer to wake up
        // esp_light_sleep_start();
      }
    }
     vTaskDelay( 65 );
  } // while(1)
} //void TrackSun()
////
void fMoveAltitude( int MoveTo )
{
  mcpwm_set_duty_in_us(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A, MoveTo);
  vTaskDelay( 12 );
}
//////
void fMoveAzimuth( int MoveTo )
{
  mcpwm_set_duty_in_us(MCPWM_UNIT_0, MCPWM_TIMER_1, MCPWM_OPR_A, MoveTo);
  vTaskDelay( 12 );
}
////
void loop() {}
////
1 Like

I don't see why this wouldn't work with an ESP32 cam board. Here's your code (mostly) simulated on an ESP32-C3 and using this library for your servos ...

Check out this project that uses the same library, a small Xiao ESP32 C3 configured with web server and servo control. He had similar issues getting the servos to work, but got them resolved.

1 Like

It's alive! Thanks to everyone who helped me with my problem. Using mcpwm or
ESP32-ESP32S2-AnalogWrite partially solve this problem, but in order for the whole circuit to work perfectly, I had to split the /servo GET request into two, for each servo. There seems to be no problem with the power supply. I'm so much happy, thank you all :smiley:

Perhaps I'll post the source code, in case someone encounters the same problem as me:

#include <WebServer.h>
#include <WiFi.h>
#include "esp_camera.h"

#include "driver/mcpwm.h"

const char* ssid = "Redmi";
const char* password = "11111111";

int ix, iy;

WebServer server(80);

const char index_html[] PROGMEM = R"rawliteral(
<body>
  <img id="img">
  <br>
  <button onclick="camUp()">capture</button>
  <br>
  <label id="label_hor">90</label>
  <br>
  <input type="range" onchange="sX()" id="hor" min="0" max="180" step="1" value="90">
  <br>
  <label id="label_ver">90</label>
  <br>
  <input type="range" onchange="sY()" id="ver" min="0" max="180" step="1" value="90">
  <script>
    function camUp() {
      img = document.getElementById("img");
      img.src = "/capture?" + new Date().getTime();
    }
    function sX() {
      let xhr = new XMLHttpRequest()
      xhr.open("GET", "/sX?h="+h.value, false)
      xhr.send()
    }
    function sY() {
      let xhr = new XMLHttpRequest()
      xhr.open("GET", "/sY?v="+v.value, false)
      xhr.send()
    }
    window.onload = () => {
      camUp()
      h = document.getElementById("hor")
      v = document.getElementById("ver")
      lv = document.getElementById("label_ver")
      lh = document.getElementById("label_hor")
      v.addEventListener("input", (event) => {
        lv.innerHTML = event.target.value
      })
      h.addEventListener("input", (event) => {
        lh.innerHTML = event.target.value
      })
    }
  </script>
</body>
)rawliteral";

void setup() {
  Serial.begin(115200);
  // OV2640
  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_2;
  config.pin_d0 = 5;
  config.pin_d1 = 18;
  config.pin_d2 = 19;
  config.pin_d3 = 21;
  config.pin_d4 = 36;
  config.pin_d5 = 39;
  config.pin_d6 = 34;
  config.pin_d7 = 35;
  config.pin_xclk = 0;
  config.pin_pclk = 22;
  config.pin_vsync = 25;
  config.pin_href = 23;
  config.pin_sscb_sda = 26;
  config.pin_sscb_scl = 27;
  config.pin_pwdn = 32;
  config.pin_reset = -1;
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_JPEG;
  config.frame_size = FRAMESIZE_QVGA;
  config.jpeg_quality = 12;
  config.fb_count = 1;

  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    return;
  }

  mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM1A, GPIO_NUM_15); // hor
  mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, GPIO_NUM_14); // ver
  
  mcpwm_config_t pwm_config;
  pwm_config.frequency = 50;    //frequency = 50Hz, i.e. for every servo motor time period should be 20ms
  pwm_config.cmpr_a = 0;    //duty cycle of PWMxA = 0
  pwm_config.cmpr_b = 0;    //duty cycle of PWMxb = 0
  pwm_config.counter_mode = MCPWM_UP_COUNTER;
  pwm_config.duty_mode = MCPWM_DUTY_MODE_0;

  mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_1, &pwm_config);  
  mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &pwm_config);

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi...");
  }
  Serial.println("Connected to WiFi");

  Serial.println(WiFi.localIP());

  server.on("/", root);
  server.on("/capture", capture);
  server.on("/sX", sX);
  server.on("/sY", sY);
  server.begin();
  Serial.println("Server started");
}

void loop() {
  server.handleClient();
}

void root() {
  Serial.println("Root");
  server.send(200, "text/html", index_html);
}

void capture() {
  Serial.println("Capture");
  
  camera_fb_t * fb = esp_camera_fb_get();
  esp_camera_fb_return(fb);
  fb = NULL;
  fb = esp_camera_fb_get();
  if (!fb) {
    Serial.println("Camera capture failed");
    return;
  }

  server.sendHeader("Content-Type", "image/jpeg");  
  server.sendContent((const char *)fb->buf, fb->len);
    
  esp_camera_fb_return(fb);
}

void sX() {
  if(server.args() > 0) {
    ix = map(server.arg("h").toInt(), 0, 180, 500, 2500);
    mcpwm_set_duty_in_us(MCPWM_UNIT_0, MCPWM_TIMER_1, MCPWM_OPR_A, ix);
  } 
}
void sY() {
  if(server.args() > 0) {
    iy = map(server.arg("v").toInt(), 0, 180, 500, 2500);
    mcpwm_set_duty_in_us(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A, iy);
  } 
}

Hey guys, how are you?

I managed to solve this interference of the servo motors with streaming.

After researching a lot, I saw that
The ESP32 camera uses timer 0 in "config", but at some point it also uses timer 1.

So the solution was to direct the servos to use timers 2 and 3 as follows:

ESP32PWM::allocateTimer(2);
ESP32PWM::allocateTimer(3);

This is using the ESPServo library, within the setup and before setting the servo frequency.

Espero ter ajudado Valeuuu ;D

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.