【注意喚起 盗撮が簡単にできる時代です。被害にあわないよう気を付けましょう】
ESP32-S3-CAMボードを購入したので、以前制作したロボットカーをバージョンアップしました。HTMLの書き方は相変らず解っておりませんが、見様見真似で書き加えてみました。ポイントは次の4点です。
1.映像ストリーミングのオンオフ
2.カメラの仰角をサーボモーターで20度ずつ変化させる
3.サーボ角をスライダーで連続変化させる(2024/10/23機能追加)
4.前照灯(LED)を2段階点灯及び消灯
5.クラクション(ホーン)を鳴らす
使用した物品です。
1.Freenove ESP32-S3 WROOM board. Flash Size 8MB.(ArduinoIDEでは「ESP32 S3 Dev Module)
2.サーボでカメラの仰角を変えられるよう「OV2640-75mm」に交換(ケーブル長75mm 画角160°)
3.TOWER PRO製「Micro Servo Digital 9g SG90」(ESP32では動かないモノもあります)
4.モータードライバー TA7291P(使い慣れてるので)
5.前照灯用 白色LED、680Ωの抵抗
6.クラクション用 小型スピーカーΦ20mm 8W
6.タミヤのミニモーター低速ギアボックス(4速) ギアレシオ 314.9:1 で使用
7.タミヤのユニバーサルプレート(210×160)・スリムタイヤ小など
8.どこでもキャスター(6kgまで)
9.単三電池×4電池ボックス
ESP32 S3 Board | TA7291P 右 | TA7291P 右 | サーボ | 前照灯 | ホーン |
---|---|---|---|---|---|
GPIO 19 | 5 | ||||
GPIO 20 | 6 | ||||
GPIO 21 | 5 | ||||
GPIO 47 | 6 | ||||
GPIO 48 | 信号Pin 黄 | ||||
GPIO 42 | 〇 | ||||
GPIO 2 | + | ||||
5V | 8(Batt 4.8V) | 8(Batt 4.8V) | 電源 赤 | ||
3V3 | 7 | 7 | |||
GND | 1 | 1 | G 濃茶 | -(670Ω)ー | 〇 |
2 Motor+ | 2 Motor+ | ||||
10 Motor- | 10 Motor- |
なお、モータードライバーTA7291Pの
2ピンと10ピン間に「0.1uF」
4ピンと8ピン間に「3kΩ」
を付けました。
Arduino IDE 2.3.2 でのスケッチです。
ESP32S3Cam_WebControl_Car.ino
#include "Arduino.h"
#include <WiFi.h>
#include "esp_camera.h"
#include "esp_http_server.h"
#include "esp_timer.h"
#include "img_converters.h"
#include "fb_gfx.h"
#include <ESP32Servo.h>
// Camera Pin Config for "ESP32 Wrover Module"
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 15
#define SIOD_GPIO_NUM 4
#define SIOC_GPIO_NUM 5
#define Y2_GPIO_NUM 11
#define Y3_GPIO_NUM 9
#define Y4_GPIO_NUM 8
#define Y5_GPIO_NUM 10
#define Y6_GPIO_NUM 12
#define Y7_GPIO_NUM 18
#define Y8_GPIO_NUM 17
#define Y9_GPIO_NUM 16
#define VSYNC_GPIO_NUM 6
#define HREF_GPIO_NUM 7
#define PCLK_GPIO_NUM 13
// Motor Pins
#define MOTOR_1_PIN_1 19
#define MOTOR_1_PIN_2 20
#define MOTOR_2_PIN_1 21
#define MOTOR_2_PIN_2 47
// Replace with your network credentials
const char* ssid = "Your_SSID";
const char* password = "Your_Password";
Servo myservo; // create servo object to control a servo
const int servoPin = 48;
const int pos0 = 0; // input min in HTML
const int pose = 90; // input max in HTML
int pos; // variable to store the servo position
// LED
const int LedPIN = 2;
// setting PWM properties
int bright = 8;
const int freq = 5000;
const int ledChannel = 2;
const int reso = 8;
// Horn
const int HornPIN = 42;
// setting PWM properties
const int loud = 516;
const int Hornfreq = 432;
const int ledChannelH = 3;
const int Hornreso = 10;
#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 camera_httpd = NULL;
httpd_handle_t stream_httpd = NULL;
static const char PROGMEM INDEX_HTML[] =
R"rawliteral(
<html>
<head>
<title>ESP32-S3_CAM Car</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body { font-family: Arial; text-align: center; margin:0px auto; padding-top: 30px;}
table { margin-left: auto; margin-right: auto; }
td { padding: 8 px; }
img { width: auto ;
max-width: 100% ;
height: auto ;
}
.button1 {
background-color: #bf94e4;
border: none;
color: black;
padding: 10px 20px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 18px;
margin: 6px 3px;
cursor: pointer;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-tap-highlight-color: rgba(0,0,0,0);
}
.button2 {
background-color: #2f4468;
border: none;
color: white;
padding: 10px 20px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 18px;
margin: 6px 3px;
cursor: pointer;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-tap-highlight-color: rgba(0,0,0,0);
}
</style>
</head>
<body>
<h1>ESP32-S3-CAM Car</h1>
<table>
<tr>
<td colspan="3" align="center">
<img id="stream" src="" >
</td>
</tr>
<tr>
<td colspan="3" align="center">
<button class="button1"
id="toggle-stream">CAMERA START
</button>
</td>
</tr>
<tr>
<td>
<input type="range" id="camAngle" min="0" max="90" value="0" />
</td>
<td>
<p class="log">Camera Angle = 0</p>
</td>
</tr>
<tr>
<td align="center">
<button class="button1"
onmousedown ="toggleCheckbox('cam-up');"
ontouchstart="toggleCheckbox('cam-up');">Camera_UP
</button>
</td>
<td align="center">
<button class="button1"
onmousedown ="toggleCheckbox('cam-dn');"
ontouchstart="toggleCheckbox('cam-dn');">Camera_DOWN
</button>
</td>
</tr>
<tr>
<td align="center">
<button class="button2"
onmousedown ="toggleCheckbox('led-up');"
ontouchstart="toggleCheckbox('led-up');">Led_LIGHTER
</button>
</td>
<td align="center">
<button class="button2"
onmousedown ="toggleCheckbox('led-dn');"
ontouchstart="toggleCheckbox('led-dn');">Led_DARKER
</button>
</td>
<td align="center">
<button class="button2"
onmousedown ="toggleCheckbox('horn');"
onmouseup ="toggleCheckbox('hstop');"
ontouchstart="toggleCheckbox('horn');"
ontouchend ="toggleCheckbox('hstop');">Horn
</button>
</td>
</tr>
<tr>
<td colspan="3" align="center">
<button class="button2"
onmousedown ="toggleCheckbox('forward');"
onmouseup ="toggleCheckbox('stop');"
ontouchstart="toggleCheckbox('forward');"
ontouchend ="toggleCheckbox('stop');">Forward
</button>
</td>
</tr>
<tr>
<td align="center">
<button class="button2"
onmousedown ="toggleCheckbox('left');"
onmouseup ="toggleCheckbox('stop');"
ontouchstart="toggleCheckbox('left');"
ontouchend ="toggleCheckbox('stop');">Turn Left
</button>
</td>
<td align="center">
<button class="button2"
onmousedown ="toggleCheckbox('stop');"
ontouchstart="toggleCheckbox('stop');">Stop
</button>
</td>
<td align="center">
<button class="button2"
onmousedown ="toggleCheckbox('right');"
onmouseup ="toggleCheckbox('stop');"
ontouchstart="toggleCheckbox('right');"
ontouchend ="toggleCheckbox('stop');">Turn Right
</button>
</td>
</tr>
<tr>
<td colspan="3" align="center">
<button class="button2"
onmousedown ="toggleCheckbox('backward');"
onmouseup ="toggleCheckbox('stop');"
ontouchstart="toggleCheckbox('backward');"
ontouchend ="toggleCheckbox('stop');">Backward
</button>
</td>
</tr>
</table>
<script>
const elementSV=document.querySelector('#camAngle');
elementSV.addEventListener('input', handleChange);
function handleChange(event){
const value=event.target.value;
document.querySelector('.log').innerHTML=`Camera Angle = ${value}`;
var xhr = new XMLHttpRequest();
xhr.open("GET", "/action?go=" + value, true);
xhr.send();
}
function toggleCheckbox(x) {
var xhr = new XMLHttpRequest();
xhr.open("GET", "/action?go=" + x, true);
xhr.send();
}
const streamButton = document.getElementById('toggle-stream')
streamButton.addEventListener("click",
function(){
const view = document.getElementById('stream')
var baseHost = document.location.origin
var streamUrl = baseHost + ':81'
const streamEnabled = streamButton.innerHTML === 'CAMERA STOP'
if(streamEnabled) {
window.stop();
streamButton.innerHTML = 'CAMERA START'
} else {
view.src = `${streamUrl}/stream`
streamButton.innerHTML = 'CAMERA STOP'
}
}
);
</script>
</body>
</html>
)rawliteral";
static esp_err_t index_handler(httpd_req_t* req) {
httpd_resp_set_type(req, "text/html");
return httpd_resp_send(req, (const char*)INDEX_HTML, strlen(INDEX_HTML));
}
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];
res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE);
if (res != ESP_OK) {
return res;
}
while (true) {
fb = esp_camera_fb_get();
if (!fb) {
Serial.println("Camera capture failed");
res = ESP_FAIL;
} else {
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("JPEG compression failed");
res = ESP_FAIL;
}
} else {
_jpg_buf = fb->buf;
_jpg_buf_len = fb->len;
}
}
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;
}
Serial.printf("MJPG: %uB\n", (uint32_t)(_jpg_buf_len));
}
return res;
}
static esp_err_t cmd_handler(httpd_req_t* req) {
char* buf;
size_t buf_len;
char variable[32] = {0,};
buf_len = httpd_req_get_url_query_len(req) + 1;
if (buf_len > 1) {
buf = (char*)malloc(buf_len);
if (!buf) {
httpd_resp_send_500(req);
return ESP_FAIL;
}
if (httpd_req_get_url_query_str(req, buf, buf_len) == ESP_OK) {
if (httpd_query_key_value(buf, "go", variable, sizeof(variable)) == ESP_OK)
{
// go on
} else {
free(buf);
httpd_resp_send_404(req);
return ESP_FAIL;
}
} else {
free(buf);
httpd_resp_send_404(req);
return ESP_FAIL;
}
free(buf);
} else {
httpd_resp_send_404(req);
return ESP_FAIL;
}
sensor_t* s = esp_camera_sensor_get();
int res = 0;
if (!strcmp(variable, "forward")) {
Serial.println("Forward");
digitalWrite(MOTOR_1_PIN_1, 1);
digitalWrite(MOTOR_1_PIN_2, 0);
digitalWrite(MOTOR_2_PIN_1, 1);
digitalWrite(MOTOR_2_PIN_2, 0);
} else if (!strcmp(variable, "left")) {
Serial.println("Left");
digitalWrite(MOTOR_1_PIN_1, 1);
digitalWrite(MOTOR_1_PIN_2, 0);
digitalWrite(MOTOR_2_PIN_1, 0);
digitalWrite(MOTOR_2_PIN_2, 1);
} else if (!strcmp(variable, "right")) {
Serial.println("Right");
digitalWrite(MOTOR_1_PIN_1, 0);
digitalWrite(MOTOR_1_PIN_2, 1);
digitalWrite(MOTOR_2_PIN_1, 1);
digitalWrite(MOTOR_2_PIN_2, 0);
} else if (!strcmp(variable, "backward")) {
Serial.println("Backward");
digitalWrite(MOTOR_1_PIN_1, 0);
digitalWrite(MOTOR_1_PIN_2, 1);
digitalWrite(MOTOR_2_PIN_1, 0);
digitalWrite(MOTOR_2_PIN_2, 1);
} else if (!strcmp(variable, "stop")) {
Serial.println("Stop");
digitalWrite(MOTOR_1_PIN_1, 0);
digitalWrite(MOTOR_1_PIN_2, 0);
digitalWrite(MOTOR_2_PIN_1, 0);
digitalWrite(MOTOR_2_PIN_2, 0);
} else if (!strcmp(variable, "cam-up")) {
pos = pos + 15;
if (pos >= pose) pos = pose;
Serial.printf("CAMERA=%d\n", pos);
myservo.write(pos);
} else if (!strcmp(variable, "cam-dn")) {
pos = pos - 15;
if (pos <= pos0) pos = pos0;
Serial.printf("CAMERA=%d\n", pos);
myservo.write(pos);
} else if (!strcmp(variable, "led-up")) {
bright += 128;
if (bright >= 255) bright = 255;
Serial.printf("LED=%d\n", bright);
ledcWrite(LedPIN, bright);
} else if (!strcmp(variable, "led-dn")) {
bright -= 128;
if (bright <= 0) bright = 0;
Serial.printf("LED=%d\n", bright);
ledcWrite(LedPIN, bright);
} else if (!strcmp(variable, "horn")) {
Serial.printf("HORN=%d\n", loud);
ledcWrite(HornPIN, loud);
delay(1000);
} else if (!strcmp(variable, "hstop")) {
ledcWrite(HornPIN, 0);
} else if (atoi(variable)>0) {
Serial.print("camAngle=");
Serial.println(atoi(variable));
myservo.write(atoi(variable));
} else {
res = -1;
}
if (res) {
return httpd_resp_send_500(req);
}
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
return httpd_resp_send(req, NULL, 0);
}
void startCameraServer() {
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.server_port = 80;
httpd_uri_t index_uri = {
.uri = "/",
.method = HTTP_GET,
.handler = index_handler,
.user_ctx = NULL
};
httpd_uri_t cmd_uri = {
.uri = "/action",
.method = HTTP_GET,
.handler = cmd_handler,
.user_ctx = NULL
};
httpd_uri_t stream_uri = {
.uri = "/stream",
.method = HTTP_GET,
.handler = stream_handler,
.user_ctx = NULL
};
if (httpd_start(&camera_httpd, &config) == ESP_OK) {
httpd_register_uri_handler(camera_httpd, &index_uri);
httpd_register_uri_handler(camera_httpd, &cmd_uri);
}
config.server_port += 1;
config.ctrl_port += 1;
if (httpd_start(&stream_httpd, &config) == ESP_OK) {
httpd_register_uri_handler(stream_httpd, &stream_uri);
}
}
void setup() {
pinMode(MOTOR_1_PIN_1, OUTPUT);
pinMode(MOTOR_1_PIN_2, OUTPUT);
pinMode(MOTOR_2_PIN_1, OUTPUT);
pinMode(MOTOR_2_PIN_2, OUTPUT);
Serial.begin(115200);
// camera init
camera_init();
// Wi-Fi connection
connectWiFi(ssid, password);
// Start streaming web server
startCameraServer();
// Camera servo
myservo.setPeriodHertz(50); // standard 50 hz servo
// 個体に応じて調整
myservo.attach(servoPin, 550, 2350); // attaches the servo
pos = pos0;
myservo.write(pos);
// configure LED PWM functionalitites
ledcAttachChannel(LedPIN, freq, reso, ledChannel);
ledcWrite(LedPIN, bright);
// configure Horn PWM functionalitites
ledcAttachChannel(HornPIN, Hornfreq, Hornreso, ledChannelH);
}
void camera_init() {
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 = 40000000;
config.frame_size = FRAMESIZE_VGA;
config.pixel_format = PIXFORMAT_JPEG; // for streaming
config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
config.fb_location = CAMERA_FB_IN_DRAM; // CAMERA_FB_IN_PSRAM;
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;
}
sensor_t* s = esp_camera_sensor_get();
// initial sensors are flipped vertically and colors are a bit saturated
s->set_vflip(s, 1); // flip it back
s->set_brightness(s, 1); // up the brightness just a bit
s->set_saturation(s, 0); // lower the saturation
s->set_hmirror(s, 1);
}
void connectWiFi(const char* ssid, const char* pwd) {
Serial.println("");
Serial.println("Connecting to WiFi network: " + String(ssid));
WiFi.disconnect(true, true);
delay(1000);
WiFi.begin(ssid, password);
Serial.println("Waiting for WIFI connection ");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println(".");
Serial.println("WiFi connected!");
Serial.print("Camera Stream Ready! Go to: http://");
Serial.println(WiFi.localIP());
delay(1000);
}
void loop() {
}
最後まで見ていただきありがとうございました。