目的
该车通过连接wifi,将视频流发送至服务器,由服务器转发给手机APP,实现直播和操控。
功能
前进、后退、左转、右转、原地转圈、视频直播。
当wifi小车到一个新的环境时,第一次接入互联网需要用手机连接ESP32-CAM的热点,通过APP发送wifi的账号和密码,连接成功后会保存到芯片中,下次开机无需再次输入。
结构说明
下位机由Arduino开发,将视频流发给Rtmp服务器(JAVA)处理转发给Android App 也可以使用微信小程序。
材料准备
- 底盘
- ESP32-CAM 开发板
-
两节18650电池
-
18650电池盒
-
LM2596S-ADJ 降压模块
-
继电器
-
L298N 电机驱动
-
舵机
下位机代码
#include "esp_camera.h"
#include <WiFi.h>
#include <WebServer.h>
#include <PubSubClient.h>
#include <EEPROM.h> //rom
#include <ArduinoJson.h>
#include <Chrono.h>
//ESP32的引脚有点少,基本上接满满的了
//闪光灯
#define FLASH_LED 4
//左轮
#define LEFT_WHEEL_A 14
#define LEFT_WHEEL_B 15
//右轮
#define RIGHT_WHEEL_A 13
#define RIGHT_WHEEL_B 12
//舵机
#define SERVO_PIN 16
#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 PCLK_GPIO_NUM 22
#define HREF_GPIO_NUM 23
/**
使用MQTT是为了不影响视频流传递速度,这里使用MQTT控制小车转向
*/
const char* mqtt_server = "";//服务器的地址
const int port=;//服务器端口号
const char* topic_name = "";//订阅的主题
const char* client_id = "";//尽量保持唯一,相同的id连接会被替代
const char* client_name="";
const char* client_psw="";
WiFiClient espClient;
PubSubClient psClient(espClient);
Chrono myChrono; //节拍器
camera_fb_t * fb = NULL;
size_t _jpg_buf_len = 0;
uint8_t * _jpg_buf = NULL;
char * part_buf[64];
//固定ESP32的IP地址
IPAddress apIP(192,168,4,1);
IPAddress gateway(192,168,4,1);
IPAddress subnet(255,255,255,0);
//使之作为服务端,可接受SSID 使ESP32能够连接互联网
WebServer server(80);
const IPAddress serverIP(?,?,?,?); //云服务器地址
uint16_t serverPort = ????; //服务器端口号
WiFiClient tcpClient; //声明一个客户端对象,用于与服务器进行连接
String ssid="";
String pass="";
int servo_current=90;
int myangle;//定义角度变量
int pulsewidth;//定义脉宽变量
int servo_status=0;//0停止 1向左 2向右
struct CONFIG {
char localSsid[20];
char localPass[20];
};
void setup() {
Serial.begin(115200);
pinMode(FLASH_LED,OUTPUT);//闪光灯
pinMode(LEFT_WHEEL_A,OUTPUT);//左轮输出
pinMode(LEFT_WHEEL_B,OUTPUT);//左轮输出
pinMode(RIGHT_WHEEL_A,OUTPUT);//右轮输出
pinMode(RIGHT_WHEEL_B,OUTPUT);//右轮输出
pinMode(SERVO_PIN,OUTPUT);//设定舵机接口为输出接口
//默认左右轮停止
digitalWrite(LEFT_WHEEL_A,LOW);
digitalWrite(LEFT_WHEEL_B,LOW);
digitalWrite(RIGHT_WHEEL_A,LOW);
digitalWrite(RIGHT_WHEEL_B,LOW);
EEPROM.begin(200);
// clearRom();
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 = 20000000;
config.pixel_format = PIXFORMAT_JPEG;
/**
* VGA极限 建议8
* config.frame_size = FRAMESIZE_VGA;
* config.jpeg_quality = 6;
* config.fb_count = 2;
*/
/**
*SVGA极限 建议12
*config.frame_size = FRAMESIZE_SVGA;
*config.jpeg_quality = 11;
*config.fb_count = 2;
*/
// config.frame_size = FRAMESIZE_SVGA;
// config.jpeg_quality = 18;
// config.fb_count = 2;
config.frame_size = FRAMESIZE_VGA;
config.jpeg_quality = 12;
config.fb_count = 2;
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
Serial.printf("Camera init failed with error 0x%x", err);
return;
}
//初次连接wifi 需要配网,成功后保存到EPPROM
getLocalInfo();//获取本地SSID
WiFi.mode(WIFI_STA);
WiFi.setSleep(false);
/**
* 依次 AP网络名称、AP网络密码、信道(1~13)、是否隐藏SSID(0不隐藏)、最大接入数量1~4
*/
WiFi.softAP("pjcoder");
delay(1000);//不加这个你试试
WiFi.softAPConfig(apIP, gateway, subnet);
IPAddress myIP = WiFi.softAPIP();
Serial.println(myIP);
//server.on("/", handleRoot);//根页面
server.on("/setssid", HTTP_GET,handleSSID);
server.begin(); //启动服务器
digitalWrite(FLASH_LED,HIGH);
psClient.setServer(mqtt_server,port);
psClient.setCallback(callback);
xTaskCreatePinnedToCore(appCpuLoop, //具体实现的函数
"appCpuLoop", //任务名称
8192, //堆栈大小
NULL, //输入参数
2, //任务优先级
NULL, //
1 //核心 0\1
);
//舵机位置初始化
for(int i=0;i<=10;i++){
servopulse(SERVO_PIN,servo_current);//引用脉冲函数
}
Serial.println("setup ...");
}
int countTime=0;
void loop() {
if(ssid.length()==0&&pass.length()==0){
server.handleClient(); //处理来自客户端的请求
}
if(ssid.length()==0&&pass.length()==0) return;
if(WiFi.status()==WL_DISCONNECTED){ //枚举6
Serial.println("WL_DISCONNECTED");
Serial.println(ssid);
Serial.println(pass);
WiFi.begin(ssid.c_str(),pass.c_str());
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
countTime++;
if(countTime>=50){
ssid="";
pass="";
Serial.println("ssid error can't connect...");
clearRom();
delay(300);
esp_restart();
}
}
Serial.println("wifi connect success....");
digitalWrite(FLASH_LED,LOW);
clearRom();
CONFIG buf;
strcpy(buf.localSsid,ssid.c_str());
strcpy(buf.localPass,pass.c_str());
EEPROM.put<CONFIG>(0,buf);
EEPROM.commit();
delay(30);
Serial.println("EPPROM SAVE");
}
if(tcpClient.connected()==0){
tcpClient.connect(serverIP, serverPort);
delay(2000);
return;
}
//获取流
fb = esp_camera_fb_get();
tcpClient.write(fb->buf,fb->len);
if(fb){
esp_camera_fb_return(fb);
fb = NULL;
}
}
void handleSSID()
{
for (uint8_t i = 0; i < server.args(); i++)
{
if(server.argName(i).startsWith("ssid")){
ssid=server.arg(i);
}
if(server.argName(i).startsWith("pass")){
pass=server.arg(i);
}
}
String res="";
StaticJsonDocument<50> doc;
doc["code"] = "666";
serializeJson(doc,res);
server.send(200, "application/json", res);
digitalWrite(FLASH_LED,LOW);
// server.send(200, "text/plain", "pjcoder");
}
void callback(char* topic, byte* payload, unsigned int length) {
String callMsg = "";
for(int i = 0; i < length; i++) {
callMsg += char(payload[i]);
}
Serial.println(callMsg);
commandJudge(callMsg);
callMsg="";
}
//本地指令执行
void commandJudge(String callMsg){
StaticJsonDocument<150> doc;
deserializeJson(doc,callMsg);
String commandStr=doc["COMMAND"];
if(commandStr.startsWith("FLASH_LED_ON")) flashLedOn(1);
if(commandStr.startsWith("FLASH_LED_OFF")) flashLedOn(0);
if(commandStr.startsWith("LEFT_WHEEL_ON")) leftWheelOn(1);
if(commandStr.startsWith("LEFT_WHEEL_OFF")) leftWheelOn(0);
if(commandStr.startsWith("RIGHT_WHEEL_ON")) rightWheelOn(1);
if(commandStr.startsWith("RIGHT_WHEEL_OFF")) rightWheelOn(0);
if(commandStr.startsWith("FORWARD_WHEEL_ON")) forwardWheelOn(1);
if(commandStr.startsWith("FORWARD_WHEEL_OFF")) forwardWheelOn(0);
if(commandStr.startsWith("BACK_WHEEL_ON")) backWhellOn(1);
if(commandStr.startsWith("BACK_WHEEL_OFF")) backWhellOn(0);
if(commandStr.startsWith("SERVO_POSITION_STOP")) servoPositionSet(3);
if(commandStr.startsWith("SERVO_POSITION_LEFT")) servoPositionSet(1);
if(commandStr.startsWith("SERVO_POSITION_RIGHT")) servoPositionSet(2);
}
//舵机位置
void servoPositionSet(int command){
if(command==3){
servo_status=3;
}
if(command==1){
servo_status=1;
}
if(command==2){
servo_status=2;
}
Serial.println(servo_status);
}
void backWhellOn(int command){
if(command==1){
//左轮正传
digitalWrite(LEFT_WHEEL_A,LOW);
digitalWrite(LEFT_WHEEL_B,HIGH);
//右轮正转
digitalWrite(RIGHT_WHEEL_A,HIGH);
digitalWrite(RIGHT_WHEEL_B,LOW);
}else{
//左右停止
digitalWrite(LEFT_WHEEL_A,LOW);
digitalWrite(LEFT_WHEEL_B,LOW);
digitalWrite(RIGHT_WHEEL_A,HIGH);
digitalWrite(RIGHT_WHEEL_B,HIGH);
}
}
void forwardWheelOn(int command){
if(command==1){
//左轮反传
digitalWrite(LEFT_WHEEL_A,HIGH);
digitalWrite(LEFT_WHEEL_B,LOW);
//右轮反转
digitalWrite(RIGHT_WHEEL_A,LOW);
digitalWrite(RIGHT_WHEEL_B,HIGH);
}else{
//左右停止
digitalWrite(LEFT_WHEEL_A,LOW);
digitalWrite(LEFT_WHEEL_B,LOW);
digitalWrite(RIGHT_WHEEL_A,HIGH);
digitalWrite(RIGHT_WHEEL_B,HIGH);
}
}
void leftWheelOn(int command){
if(command==1){
//右轮正转
digitalWrite(RIGHT_WHEEL_A,HIGH);
digitalWrite(RIGHT_WHEEL_B,LOW);
//左轮返传
digitalWrite(LEFT_WHEEL_A,HIGH);
digitalWrite(LEFT_WHEEL_B,LOW);
}else{
//左右停止
digitalWrite(LEFT_WHEEL_A,LOW);
digitalWrite(LEFT_WHEEL_B,LOW);
digitalWrite(RIGHT_WHEEL_A,HIGH);
digitalWrite(RIGHT_WHEEL_B,HIGH);
}
}
void rightWheelOn(int command){
if(command==1){
//左轮正传
digitalWrite(LEFT_WHEEL_A,LOW);
digitalWrite(LEFT_WHEEL_B,HIGH);
//右轮反转
digitalWrite(RIGHT_WHEEL_A,LOW);
digitalWrite(RIGHT_WHEEL_B,HIGH);
}else{
//左右停止
digitalWrite(LEFT_WHEEL_A,LOW);
digitalWrite(LEFT_WHEEL_B,LOW);
digitalWrite(RIGHT_WHEEL_A,HIGH);
digitalWrite(RIGHT_WHEEL_B,HIGH);
}
}
void flashLedOn(int command){
if(command==1){
digitalWrite(FLASH_LED,HIGH);
}else{
digitalWrite(FLASH_LED,LOW);
}
}
void reconnect() {//等待,直到连接上服务器
if (!psClient.connected()) {//如果没有连接上
if (psClient.connect(client_id,client_name,client_psw,0,2,0,0,1)) {
psClient.subscribe(topic_name);//接收外来的数据时的intopic
Serial.println("{\"MQTT_STATE\":\"SUCCESS\"}");
// delay(30);
} else {
Serial.println("{\"MQTT_STATE\":\"FAIL\"}");//连接失败
// delay(500);
}
}
}
void getLocalInfo(){
CONFIG buf;
EEPROM.get<CONFIG>(0, buf);
String _ssid=String(buf.localSsid);
String _pass=String(buf.localPass);
if(_ssid.length()==0||_pass.length()==0){
Serial.println("local empty");
}else{
Serial.println("getLocalInfo SUCCESS");
ssid=_ssid;
pass=_pass;
Serial.print("get account:");
}
}
void appCpuLoop(void *pvParameters){
while (1){
delay(2);//通过delay协调两个核心各自正常运作
if (myChrono.hasPassed(30)) {
myChrono.restart(); // restart the Chrono
reconnect();
psClient.loop();
if(servo_status==3){
servo_status=0;
continue;
}
if(servo_status==1){
if(servo_current<180){
servo_current=servo_current+1;
servopulse(SERVO_PIN,servo_current);//引用脉冲函数
}
}
if(servo_status==2){
if(servo_current>0){
servo_current=servo_current-1;
servopulse(SERVO_PIN,servo_current);//引用脉冲函数
}
}
}
}
vTaskDelete(NULL);
}
void clearRom(){
for (int i = 0; i < 4096; i++)
EEPROM.write(i, 0);
EEPROM.commit();
}
void servopulse(int servopin,int myangle)//定义一个脉冲函数
{
pulsewidth=(myangle*11)+500;//将角度转化为500-2480 的脉宽值
digitalWrite(servopin,HIGH);//将舵机接口电平至高
delayMicroseconds(pulsewidth);//延时脉宽值的微秒数
digitalWrite(servopin,LOW);//将舵机接口电平至低
delay(20-pulsewidth/1000);
}