App 為 V7RC 有 IOS 及 android 版本
底下為 ESP_FPV_motor 擴充板的引腳說明
////////////////////////////////////////////////////////////////////////
// ESP32CAM FPV car (Using ESP32CAM + V7RC) by Mason E & D (2021/4/1)
/////////////////////////////////////////////////////////////////////
// Author : Mason 2021/4/1, masonchen1003@gmail.com
// FB : https://www.facebook.com/mason.chen.1420
// Licensed under the Creative Commons - Attribution - Non-Commercial license.
// V202 : Increase the fps
// V7RC protocol setting
// Company: V7 IDEA TECHNOLOGY LTD.
// Author: Louis Chuang
#include <WiFi.h>
#include "esp_timer.h"
#include "img_converters.h"
#include "Arduino.h"
#include "fb_gfx.h"
#include "soc/soc.h" //disable brownout problems
#include "soc/rtc_cntl_reg.h" //disable brownout problems
#include "esp_http_server.h"
//Replace with your AP_setting
const char* ssid = "ESP_car";
const char* password = "12345678";
#define udp_on
#ifdef udp_on
#include <WiFiUdp.h>
const int udpPort = 6188; // V7RC port setting
bool udp_packet_not_empty = false;
bool udp_packet_loaded = false;
WiFiUDP udp;
char packetBuffer[255];
char ReplyBuffer[] = "acknowledged";
#endif
// 宣告馬達驅動 drv8833 or L9110 用到的腳位
//#define MotorA1 12
//#define MotorA2 13
//#define MotorB1 15
//#define MotorB2 14
#define MotorA1 14
#define MotorA2 15
#define MotorB1 12
#define MotorB2 13
int speed_FB = 250; // 控制前進、後退的轉速
int speed_LR = 100; // 控制左右轉的轉速
#define PWDN_GPIO_NUM 32
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 0
#define SIOD_GPIO_NUM 26
#define SIOC_GPIO_NUM 27
#define Y9_GPIO_NUM 35
#define Y8_GPIO_NUM 34
#define Y7_GPIO_NUM 39
#define Y6_GPIO_NUM 36
#define Y5_GPIO_NUM 21
#define Y4_GPIO_NUM 19
#define Y3_GPIO_NUM 18
#define Y2_GPIO_NUM 5
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 23
#define PCLK_GPIO_NUM 22
typedef struct {
size_t size; //number of values used for filtering
size_t index; //current value index
size_t count; //value count
int sum;
int * values; //array to be filled with values
} ra_filter_t;
#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;
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];
int64_t fr_start = 0;
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;
}
while(true){
fb = esp_camera_fb_get();
if (!fb) {
Serial.println("Camera capture failed");
res = ESP_FAIL;
} else {
fr_start = esp_timer_get_time();
_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;
}
}
last_frame = 0; //
return res;
}
void startCameraServer(){
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
httpd_uri_t stream_uri = {
.uri = "/",
.method = HTTP_GET,
.handler = stream_handler,
.user_ctx = NULL
};
Serial.printf("Starting stream server on port: '%d'\n", config.server_port);
if (httpd_start(&stream_httpd, &config) == ESP_OK) {
httpd_register_uri_handler(stream_httpd, &stream_uri);
}
}
void forward() {
digitalWrite(MotorA1, HIGH);
ledcWrite(7, 255-speed_FB); // change from 9 -> 7, 2023/9/26
digitalWrite(MotorB1, HIGH);
ledcWrite(10, 255-speed_FB );
delay(50);
}
void backward() {
digitalWrite(MotorA1, LOW);
ledcWrite(7, speed_FB ); // change from 9 -> 7, 2023/9/26
digitalWrite(MotorB1, LOW);
ledcWrite(10, speed_FB );
delay(50);
}
void turn_left() {
digitalWrite(MotorA1, HIGH);
ledcWrite(7, 255-speed_LR ); // change from 9 -> 7, 2023/9/26
digitalWrite(MotorB1, LOW);
ledcWrite(10, speed_LR );
delay(50);
}
void turn_right() {
digitalWrite(MotorA1, LOW);
ledcWrite(7, speed_LR ); // change from 9 -> 7, 2023/9/26
digitalWrite(MotorB1, HIGH);
ledcWrite(10, 255-speed_LR );
delay(50);
}
void stop_all() {
digitalWrite(MotorA1, LOW);
ledcWrite(7, 0 ); // change from 9 -> 7, 2023/9/26
digitalWrite(MotorB1, LOW);
ledcWrite(10, 0 );
}
//LED 閃光燈設定
int freq = 20000;
int ledCHannel = 4;
int res = 8;
const int ledPin = 4;
int brightness;
void setup() {
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector
Serial.begin(115200); // initialize serial communication
pinMode(4, OUTPUT); //LED
ledcSetup(ledCHannel, freq, res);
ledcAttachPin(ledPin, ledCHannel);
pinMode(MotorA1, OUTPUT); //MotorA
pinMode(MotorB1, OUTPUT); //MotorB
// change from 9 -> 7, 2023/9/26
ledcSetup(7, 50, 8); // 宣告 L9110 的另一根腳位為 PWM,8 bits 0-255
ledcAttachPin(MotorA2, 7);
ledcSetup(10, 50, 8);
ledcAttachPin(MotorB2, 10);
stop_all();
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_sscb_sda = SIOD_GPIO_NUM;
config.pin_sscb_scl = SIOC_GPIO_NUM;
config.pin_pwdn = PWDN_GPIO_NUM;
config.pin_reset = RESET_GPIO_NUM;
config.xclk_freq_hz = 10000000;
config.pixel_format = PIXFORMAT_JPEG;
if(psramFound()){
config.frame_size = FRAMESIZE_VGA; //FRAMESIZE_UXGA, FRAMESIZE_ + QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA
config.jpeg_quality = 15; //10-63 lower number means higher quality
config.fb_count = 2;
} else {
config.frame_size = FRAMESIZE_VGA; // SVGA
config.jpeg_quality = 15; // 12
config.fb_count = 1;
}
// 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();
s->set_hmirror(s, 1); // 0 = disable , 1 = enable
s->set_vflip(s, 1); // 0 = disable , 1 = enable
s->set_framesize(s, FRAMESIZE_VGA);
// Wi-Fi connection
WiFi.persistent(false);
WiFi.softAP(ssid, password);
#ifdef udp_on
//This initializes udp and transfer buffer
udp.begin(udpPort);
#endif
// Start streaming web server
startCameraServer();
}
void loop() {
#ifdef udp_on
while (true) {
udp_packet_not_empty = (udp.parsePacket() > 0);
if (udp_packet_not_empty) {
udp_packet_loaded = true;
// udp.read(packetBuffer, UDP_TX_PACKET_MAX_SIZE);
udp.read(packetBuffer, 1024);
}
else if (!udp_packet_not_empty && udp_packet_loaded) { // read the last packet
udp_packet_loaded = false;
break;
}
else {
// error
}
}
Serial.println(packetBuffer);
String receive_data = String(packetBuffer);
receive_data.trim(); // Remove some characters
// decode V7RC protocol
if (receive_data.startsWith("SRT")){
int ch1_data=(receive_data.substring(3,7)).toInt();
int ch2_data=(receive_data.substring(7,11)).toInt();
int ch3_data=(receive_data.substring(11,15)).toInt();
int ch4_data=(receive_data.substring(15,19)).toInt();
if (abs(ch1_data-1500)<100 && abs(ch2_data-1500)<100) {
stop_all() ;
} else if (abs(ch1_data-1500) > abs(ch2_data-1500)) {
if (ch1_data > 1500) { // r
turn_right();
} else { // l
turn_left();
}
} else {
if (ch2_data > 1500) {
forward();
} else {
backward();
}
}
if (ch3_data >= 1500) {
brightness = map(ch3_data, 1500, 2000, 0, 255);
ledcWrite(ledCHannel, brightness);
}
}
udp_packet_not_empty = false;
udp_packet_loaded = false;
#endif
delay(1);
}