mercredi 12 février 2020

Mouvement panoramique avec ESP32-CAM et servomoteur


Je continue de bien m'amuser avec l'ESP32-CAM. Cette fois, j'ai installé la caméra sur un servomoteur afin qu'elle puisse effectuer un mouvement panoramique horizontal­. J'ai ainsi obtenu une caméra de surveillance qui balaie un champ de vision beaucoup plus large.

À cette étape du projet, le servomoteur tourne en continu aussitôt qu'un utilisateur visionne l'image vidéo diffusée par le web server de l'ESP32-CAM. Il sera certainement intéressant, comme développement ultérieur, d'ajouter un deuxième servomoteur ainsi que des boutons de contrôle sur la page web, ce qui permettrait à l'utilisateur de positionner la caméra à distance (pan & tilt).

Connexions

J'ai branché le servomoteur à la broche GPIO 2 de l'ESP32-CAM.

Pour l'exécution d'un sketch simple dans lequel l'ESP32-CAM ne faisait rien d'autre que faire tourner le servomoteur, j'ai pu alimenter le servomoteur en le branchant à la même alimentation 5 V que l'ESP32-CAM. Mais aussitôt que j'essayais de brancher l'ESP32-CAM au réseau WiFi, le "brownout detector" interrompait le programme au démarrage. Pour cette raison, j'ai alimenté le servomoteur avec une alimentation 5 V distincte.


Test du servomoteur

Il y a quelques mois, j'avais exploré la production d'un signal PWM avec un ESP32; ça m'a facilité la tâche pour produire ce sketch qui ne fait rien d'autre que faire tourner le servomoteur dans un sens, puis dans l'autre. Dans mon cas, le servomoteur atteignait ses positions extrêmes pour un rapport cyclique d'environ 3550 et 8000, mais ces valeurs peuvent varier un peu d'un servomoteur à l'autre.

-
/*
L'ESP32-CAM fait tourner un servomoteur dans
un sens, puis dans l'autre. Le servomoteur est
branché à la broche GPIO 2.
Plus de détails:
https://electroniqueamateur.blogspot.com/2020/02/mouvement-panoramique-avec-esp32-cam-et.html
*/
void setup() {
Serial.begin(115200);
ledcSetup(2, 50, 16); // canal, fréquence, résolution
ledcAttachPin(2, 2); // pin, channel
}
void loop() {
// les positions varient de 3550 à 8000.
for (int i = 3550; i <= 8000; i++) {
Serial.println(i);
ledcWrite(2, i);
delay(5);
}
for (int i = 8000; i >= 3550; i--) {
ledcWrite(2, i);
delay(5);
}
}
-

Sketch: web server avec mouvement panoramique

Voici finalement le sketch qui permet d'observer l'image vidéo sur un navigateur web. Il s'agit essentiellement de mon sketch de web server de base, auquel j'ai ajouté quelques lignes pour gérer le servomoteur.

Aux lignes 29 à 36, je définis quelques constantes (broche à laquelle le servomoteur est connecté, position minimale désirée, position maximale désirée, vitesse désirée) et deux variables globales: la position du servomoteur et la direction dans laquelle il tourne.


Aux lignes 223 à 225, j'initialise le signal PWM et l'assigne à la broche à laquelle j'ai branché le servomoteur.


La position du servomoteur est réglée à la ligne 103. Les lignes suivantes calculent la prochaine position et vérifient s'il est temps d'inverser le sens de rotation..



La vidéo ci-dessous montre le résultat. Le mouvement du servomoteur n'est pas parfaitement régulier (il semble ralentir à certains moments) et le support en papier que j'ai bricolé pour ma ESP32-CAM manque de rigidité (il se déforme un peu pendant la rotation, ce qui ajoute une oscillation verticale à l'image). Mais comme prototype, ça me semble assez bien.




-
/*
Affichage dans une page web de l'image vidéo
d'une ESP32-CAM, avec lent panoramique horizontal
obtenu grâce à un servomoteur.
Pour plus de détails:
https://electroniqueamateur.blogspot.com/2020/02/mouvement-panoramique-avec-esp32-cam-et.html
*/
#include "esp_camera.h"
#include <WiFi.h>
#include "esp_http_server.h"
// paramètres de votre réseau WiFi
const char* ssid = "**********";
const char* password = "***********";
#define PART_BOUNDARY "123456789000000000000987654321"
static const char* _STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY;
static const char* _STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";
static const char* _STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n";
httpd_handle_t stream_httpd = NULL;
httpd_handle_t camera_httpd = NULL;
int numero_port; // numéro du port du stream server
const int broche_servo = 2; // servomoteur branché à GPIO 2
// rapport cyclique correspondant à la position minimale du servomoteur
const int position_min = 3700;
// rapport cyclique correspondant à la position maximale du servomoteur
const int position_max = 7000;
const int vitesse_servo = 10;
int position_servo = position_min;
bool direction_servo = 1; // 1 à l'allée, 0 au retour
// ********************************************************
// stream_handler: routine qui gère le streaming vidéo
static esp_err_t stream_handler(httpd_req_t *req) {
camera_fb_t * fb = NULL;
esp_err_t res = ESP_OK;
size_t _jpg_buf_len = 0;
uint8_t * _jpg_buf = NULL;
char * part_buf[64];
static int64_t last_frame = 0;
if (!last_frame) {
last_frame = esp_timer_get_time();
}
res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE);
if (res != ESP_OK) {
return res;
}
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
while (true) {
fb = esp_camera_fb_get();
if (!fb) {
Serial.println("Echec de la capture de camera");
res = ESP_FAIL;
} else {
if (fb->width > 400) {
if (fb->format != PIXFORMAT_JPEG) {
bool jpeg_converted = frame2jpg(fb, 80, &_jpg_buf, &_jpg_buf_len);
esp_camera_fb_return(fb);
fb = NULL;
if (!jpeg_converted) {
Serial.println("Echec de la compression JPEG");
res = ESP_FAIL;
}
} else {
_jpg_buf_len = fb->len;
_jpg_buf = fb->buf;
}
}
}
if (res == ESP_OK) {
size_t hlen = snprintf((char *)part_buf, 64, _STREAM_PART, _jpg_buf_len);
res = httpd_resp_send_chunk(req, (const char *)part_buf, hlen);
}
if (res == ESP_OK) {
res = httpd_resp_send_chunk(req, (const char *)_jpg_buf, _jpg_buf_len);
}
if (res == ESP_OK) {
res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
}
if (fb) {
esp_camera_fb_return(fb);
fb = NULL;
_jpg_buf = NULL;
} else if (_jpg_buf) {
free(_jpg_buf);
_jpg_buf = NULL;
}
if (res != ESP_OK) {
break;
}
// ------- gestion du mouvement du servomoteur ----------------
ledcWrite(broche_servo, position_servo);
if (direction_servo){
position_servo = position_servo + vitesse_servo;
if (position_servo > position_max){
direction_servo = 0;
}
}
else{
position_servo = position_servo - vitesse_servo;
if (position_servo < position_min){
direction_servo = 1;
}
}
}
last_frame = 0;
return res;
}
// ********************************************************
// web_handler: construction de la page web
static esp_err_t web_handler(httpd_req_t *req) {
httpd_resp_set_type(req, "text/html");
httpd_resp_set_hdr(req, "Content-Encoding", "identity");
sensor_t * s = esp_camera_sensor_get();
char pageWeb[175] = "";
strcat(pageWeb, "<!doctype html> <html> <head> <title id='title'>ESP32-CAM</title> </head> <body> <img id='stream' src='http://");
// l'adresse du stream server (exemple: 192.168.0.145:81):
char adresse[20] = "";
sprintf (adresse, "%d.%d.%d.%d:%d", WiFi.localIP()[0], WiFi.localIP()[1], WiFi.localIP()[2], WiFi.localIP()[3], numero_port);
strcat(pageWeb, adresse);
strcat(pageWeb, "/stream'> </body> </html>");
int taillePage = strlen(pageWeb);
return httpd_resp_send(req, (const char *)pageWeb, taillePage);
}
// ********************************************************
// startCameraServer: démarrage du web server et du stream server
void startCameraServer() {
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
httpd_uri_t index_uri = {
.uri = "/",
.method = HTTP_GET,
.handler = web_handler,
.user_ctx = NULL
};
httpd_uri_t stream_uri = {
.uri = "/stream",
.method = HTTP_GET,
.handler = stream_handler,
.user_ctx = NULL
};
Serial.printf("Demarrage du web server sur le port: '%d'\n", config.server_port);
if (httpd_start(&camera_httpd, &config) == ESP_OK) {
httpd_register_uri_handler(camera_httpd, &index_uri);
}
config.server_port += 1;
config.ctrl_port += 1;
Serial.printf("Demarrage du stream server sur le port: '%d'\n", config.server_port);
if (httpd_start(&stream_httpd, &config) == ESP_OK) {
httpd_register_uri_handler(stream_httpd, &stream_uri);
}
numero_port = config.server_port;
}
// ********************************************************
// initialisation de la caméra, connexion au réseau WiFi.
void setup() {
Serial.begin(115200);
Serial.println();
Serial.println("====");
// définition des broches pour le modèle AI Thinker - ESP32-CAM
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; //YUV422|GRAYSCALE|RGB565|JPEG
config.frame_size = FRAMESIZE_VGA; // QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA
config.jpeg_quality = 10; // 0-63 ; plus bas = meilleure qualité
config.fb_count = 2; // nombre de frame buffers
// initialisation de la caméra
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
Serial.printf("Echec de l'initialisation de la camera, erreur 0x%x", err);
return;
}
sensor_t * s = esp_camera_sensor_get();
// initialisation du servo moteur
ledcSetup(2, 50, 16); // canal, fréquence, résolution
ledcAttachPin(broche_servo, 2); // broche, canal
Serial.println("");
Serial.print("Connexion au reseau WiFi: ");
Serial.println(ssid);
delay(100);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(250);
}
Serial.println("WiFi connecte");
Serial.println("");
delay(100);
startCameraServer();
Serial.print("La camera est prete. Utilisez 'http://");
Serial.print(WiFi.localIP());
Serial.println("' pour vous connecter.");
}
// ********************************************************
// loop ne fait rien!
void loop() {
delay(10000);
}
-

À lire également:

Première utilisation de l'ESP32-CAM avec l'IDE Arduino , un web server minimaliste , enregistrer des photos sur la carte microSD, les LEDs de l'ESP32-CAM, time-lapse avec l'ESP32-CAM.

Yves Pelletier (TwitterFacebook)

2 commentaires:

  1. Bonsoir, je trouve votre article très intéressant, cependant, j'aurais quelques questions :
    Je possède également une ESP32-cam, ainsi que 2 servos SG90. j'ai réalisé un boitiers PAN/TILT et j'aurais aimé savoir comment faire pour piloter ces servos via des bouton multi-directionnel sur une page WEB (exemple placé sous la vidéo de la cam), mais il fadrait que jeedom puisse recupérer ces commandes... merci à vous

    Chris

    RépondreSupprimer
  2. Bonjour , ce serait sympa que depuis la page web on puisse régler les points min et max ainsi que la vitesse sans devoir a chaque fois devoir reprogrammer , perso pas assez bon en prog pour le faire ...

    RépondreSupprimer