ESP32-CAM 直播远程控制Wifi小车-下位机(一)

目的

该车通过连接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);
}

  • 15
    点赞
  • 161
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
以下是使用ESP-NOW协议远程控制ESP32板子上LED的步骤: 1. 在ESP32板子上,创建一个新的Arduino IDE项目。 2. 在程序中导入ESP-NOW库: ``` #include <esp_now.h> ``` 3. 定义一个结构体来保存ESP-NOW发送数据的MAC地址和数据: ``` typedef struct __attribute__((packed)) { uint8_t mac[6]; uint8_t data; } __packed message_t; ``` 4. 在setup()函数中初始化ESP-NOW库: ``` WiFi.mode(WIFI_STA); // 设置为STA模式 if (esp_now_init() != ESP_OK) { // 初始化ESP-NOW Serial.println("Error initializing ESP-NOW"); return; } ``` 5. 在setup()函数中设置要发送数据的MAC地址: ``` esp_now_peer_info_t peer; memcpy(peer.peer_addr, MAC_ADDRESS, 6); // 设置MAC地址 peer.channel = 0; // 设置通道 peer.encrypt = false; // 不加密 if (esp_now_add_peer(&peer) != ESP_OK) { // 添加对等节点 Serial.println("Failed to add peer"); return; } ``` 6. 在loop()函数中检测是否有接收到消息: ``` message_t receivedMessage; int packetSize = sizeof(receivedMessage); if (esp_now_recv(NULL, &receivedMessage, &packetSize) == ESP_OK) { // 接收数据成功 Serial.print("Received message: "); Serial.println(receivedMessage.data); digitalWrite(LED_PIN, receivedMessage.data); // 控制LED } ``` 7. 在loop()函数中定期发送数据: ``` message_t sendMessage; memcpy(sendMessage.mac, MAC_ADDRESS, 6); // 设置MAC地址 sendMessage.data = digitalRead(BUTTON_PIN); // 设置数据为按钮状态 esp_err_t result = esp_now_send(NULL, &sendMessage, sizeof(sendMessage)); // 发送数据 if (result == ESP_OK) { Serial.println("Sent message successfully"); } else if (result == ESP_ERR_ESPNOW_NOT_INIT) { Serial.println("ESP-NOW not initialized"); } else if (result == ESP_ERR_ESPNOW_ARG) { Serial.println("Invalid argument"); } else if (result == ESP_ERR_ESPNOW_INTERNAL) { Serial.println("Internal error"); } else if (result == ESP_ERR_ESPNOW_NO_MEM) { Serial.println("Out of memory"); } else if (result == ESP_ERR_ESPNOW_NOT_FOUND) { Serial.println("Peer not found"); } else { Serial.println("Unknown error"); } ``` 8. 在setup()函数中设置LED和按钮的GPIO: ``` pinMode(LED_PIN, OUTPUT); pinMode(BUTTON_PIN, INPUT_PULLUP); ``` 9. 在程序中定义MAC_ADDRESS常量,该常量用于设置要发送数据的MAC地址: ``` #define MAC_ADDRESS {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC} // 设置MAC地址 ``` 注意:需要将MAC_ADDRESS替换为实际的MAC地址。可以使用ESP32板子上的“ESP-NOW Example”示例程序来获取MAC地址。 完成以上步骤后,可以将程序上传到ESP32板子上,并使用另一个ESP32板子或其他支持ESP-NOW协议的设备来远程控制LED。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值