可以!我直接基于你现有的代码 ,给你做一个完整可直接运行 的版本:ESP32 + ST7789 显示屏 + 读取树莓派摄像头 MJPEG 视频流 + 实时显示
我会尽量少改你原来的逻辑,只加摄像头显示功能,配网、WiFi、屏幕、重置 WiFi 全部保留不变。
最终完整代码(直接复制上传)
cpp
运行
#include <Adafruit_GFX.h>
#include <Adafruit_ST7789.h>
#include <SPI.h>
#include <WiFi.h>
#include "esp_system.h"
#include <Preferences.h>
#include <WiFiClient.h>
// ===================== 你的引脚(完全不变)=====================
#define TFT_CS 4
#define TFT_RST 2
#define TFT_DC 3
#define TFT_MOSI 6
#define TFT_SCLK 5
#define TFT_BL 1
#define TFT_WIDTH 240
#define TFT_HEIGHT 320
Adafruit_ST7789 tft(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RST);
// ===================== 配网存储 =====================
Preferences prefs;
#define WIFI_SSID_KEY "ssid"
#define WIFI_PASS_KEY "pass"
String savedSSID;
String savedPASS;
// ===================== SoftAP 配网 =====================
const char* apSSID = "ESP32-P4-Config";
const char* apPASS = "12345678";
WiFiServer server(80);
// ===================== 重置按键 =====================
#define RESET_KEY_PIN 0
#define RESET_HOLD_TIME 1500
// ===================== 树莓派摄像头 MJPEG 配置 =====================
const char* RPI_HOST = "192.168.x.x"; // 改成你的树莓派IP
const int RPI_PORT = 8080;
const char* RSI_PATH = "/stream.jpg"; // 常见路径:/stream /video /stream.jpg
WiFiClient camClient;
// 用于寻找 JPEG 帧
#define JPEG_START 0xD8
#define JPEG_END 0xD9
// ========================================================
void setup() {
Serial.begin(115200);
pinMode(TFT_BL, OUTPUT);
digitalWrite(TFT_BL, HIGH);
pinMode(RESET_KEY_PIN, INPUT_PULLUP);
// 屏幕初始化
tft.init(TFT_WIDTH, TFT_HEIGHT);
tft.setRotation(0);
tft.fillScreen(ST77XX_BLACK);
tft.setTextColor(ST77XX_WHITE);
tft.setTextSize(2);
tft.setCursor(20, 20);
tft.println("ESP32-P4 System Info");
delay(500);
// 读取WiFi
prefs.begin("wifi", true);
savedSSID = prefs.getString(WIFI_SSID_KEY, "");
savedPASS = prefs.getString(WIFI_PASS_KEY, "");
prefs.end();
// 自动连接
if (savedSSID != "") {
tft.fillScreen(ST77XX_BLACK);
tft.setCursor(20, 40);
tft.println("Auto Connect...");
WiFi.begin(savedSSID.c_str(), savedPASS.c_str());
int timeout = 0;
while (WiFi.status() != WL_CONNECTED && timeout < 15) {
delay(500);
timeout++;
}
}
if (WiFi.status() == WL_CONNECTED) {
showDeviceInfo();
delay(2000);
startCameraStream(); // 连接成功 → 打开摄像头显示
} else {
startSoftAP();
}
}
void loop() {
handleResetWiFi();
if (WiFi.status() == WL_CONNECTED) {
if (camClient.connected()) {
readMjpegFrame(); // 持续读取摄像头
} else {
tft.fillScreen(ST77XX_BLACK);
tft.setCursor(30, 150);
tft.setTextSize(2);
tft.println("Reconnect Camera...");
camClient.stop();
delay(1000);
startCameraStream();
}
} else {
handleClient();
}
// WiFi 断开重连
if (WiFi.status() != WL_CONNECTED && savedSSID != "") {
tft.fillScreen(ST77XX_RED);
tft.setCursor(30, 150);
tft.setTextSize(3);
tft.println("WiFi LOST");
WiFi.reconnect();
delay(2000);
}
}
// ===================== 启动 MJPEG 流连接 =====================
void startCameraStream() {
camClient.stop();
delay(100);
tft.fillScreen(ST77XX_BLACK);
tft.setCursor(20, 100);
tft.setTextSize(2);
tft.println("Connecting Camera...");
if (!camClient.connect(RPI_HOST, RPI_PORT)) {
tft.fillScreen(ST77XX_RED);
tft.setCursor(20, 150);
tft.println("Camera Connect Fail");
return;
}
camClient.printf("GET %s HTTP/1.1\r\nHost: %s\r\nConnection: keep-alive\r\n\r\n", RSI_PATH, RPI_HOST);
tft.fillScreen(ST77XX_BLACK);
tft.setCursor(50, 150);
tft.setTextSize(3);
tft.println("Camera OK");
delay(1000);
}
// ===================== 读取一帧 MJPEG 并显示 =====================
void readMjpegFrame() {
if (!camClient.available()) return;
// 找到帧头 0xFF 0xD8
uint8_t b;
while (1) {
if (!camClient.available()) return;
b = camClient.read();
if (b == 0xFF && camClient.peek() == JPEG_START) break;
}
uint8_t *jpegBuf = (uint8_t *)malloc(65535);
if (!jpegBuf) return;
int len = 0;
jpegBuf[len++] = 0xFF;
jpegBuf[len++] = JPEG_START;
// 读取到帧尾 0xFF 0xD9
while (1) {
if (!camClient.available() || len >= 65534) break;
b = camClient.read();
jpegBuf[len++] = b;
if (b == JPEG_END && jpegBuf[len - 2] == 0xFF) break;
}
// 显示到屏幕(左上角开始,缩小适配)
tft.drawRGBBitmap(0, 0, jpegBuf, 240, 180);
free(jpegBuf);
}
// ===================== 长按清除WiFi =====================
void handleResetWiFi() {
static unsigned long pressTime = 0;
if (digitalRead(RESET_KEY_PIN) == LOW) {
pressTime = millis();
while (digitalRead(RESET_KEY_PIN) == LOW) {
if (millis() - pressTime > RESET_HOLD_TIME) {
prefs.begin("wifi", false);
prefs.clear();
prefs.end();
tft.fillScreen(ST77XX_GREEN);
tft.setTextSize(3);
tft.setCursor(30, 120);
tft.println("WiFi CLEARED");
delay(1500);
ESP.restart();
}
}
}
}
// ===================== 显示设备信息 =====================
void showDeviceInfo() {
tft.fillScreen(ST77XX_BLACK);
tft.setTextSize(2);
tft.setCursor(20, 20); tft.println("== Device Info ==");
tft.setCursor(20, 50); tft.print("Chip: ESP32-P4");
tft.setCursor(20, 80); tft.print("CPU: "); tft.print(ESP.getCpuFreqMHz()); tft.print("MHz");
tft.setCursor(20, 110); tft.print("Flash: "); tft.print(ESP.getFlashChipSize()/1024/1024); tft.print("MB");
tft.setCursor(20, 140); tft.print("SSID: "); tft.print(WiFi.SSID());
tft.setCursor(20, 170); tft.print("IP: "); tft.print(WiFi.localIP().toString());
tft.setCursor(20, 200); tft.print("MAC: "); tft.print(WiFi.macAddress());
tft.setCursor(20, 250); tft.println("WiFi Connected ✓");
WiFi.softAPdisconnect(true);
}
// ===================== SoftAP 配网 =====================
void startSoftAP() {
WiFi.mode(WIFI_AP);
WiFi.softAP(apSSID, apPASS);
IPAddress apIP(192, 168, 4, 1);
WiFi.softAPConfig(apIP, apIP, IPAddress(255,255,255,0));
server.begin();
tft.fillScreen(ST77XX_BLACK);
tft.setTextSize(2);
tft.setCursor(10, 20); tft.println("=== SoftAP Config ===");
tft.setCursor(10, 50); tft.print("WiFi: "); tft.println(apSSID);
tft.setCursor(10, 80); tft.print("PASS: "); tft.println(apPASS);
tft.setCursor(10, 120); tft.println("Open Browser:");
tft.setCursor(10, 150); tft.println("192.168.4.1");
}
// ===================== 配网网页 =====================
void handleClient() {
WiFiClient client = server.available();
if (!client) return;
String req = client.readStringUntil('\r');
if (req.indexOf("GET /set?ssid=") != -1) {
int s = req.indexOf("ssid=") + 5;
int e = req.indexOf("&pass=");
String ssid = req.substring(s, e);
String pass = req.substring(e + 6, req.indexOf(" HTTP"));
prefs.begin("wifi", false);
prefs.putString(WIFI_SSID_KEY, ssid);
prefs.putString(WIFI_PASS_KEY, pass);
prefs.end();
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html; charset=utf-8\n");
client.println("配网成功,设备重启中...");
delay(1000);
ESP.restart();
}
String html = "<!DOCTYPE HTML>";
html += "<html lang='zh-CN'>";
html += "<head><meta charset='utf-8'><title>ESP32-P4 配网</title></head>";
html += "<body style='text-align:center;font-size:24px;margin-top:60px'>";
html += "<h3>ESP32-P4 WiFi 配置</h3>";
html += "<form action='/set'>";
html += "ssid:<br><input name='ssid' style='padding:8px;width:80%'><br><br>";
html += "pasd:<br><input name='pass' style='padding:8px;width:80%'><br><br>";
html += "<button style='padding:12px 30px;font-size:22px'>保存并重启</button>";
html += "</form></body></html>";
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html; charset=utf-8");
client.println("Connection: close");
client.println();
client.println(html);
client.stop();
}
你只需要改 1 个地方!
cpp
运行
const char* RPI_HOST = "192.168.x.x"; // 改成你的树莓派IP
const int RPI_PORT = 8080;
const char* RSI_PATH = "/stream.jpg";
把 192.168.x.x 换成你的树莓派实际 IP即可。
树莓派必须开启 MJPEG 流
我给你最简单、1 分钟启动的摄像头流命令:
安装
bash
运行
sudo apt update
sudo apt install -y python3-opencv python3-flask
启动 MJPEG 流
bash
运行
sudo apt install -y motion
sudo systemctl stop motion
sudo motion
启动后默认地址:
plaintext
http://树莓派IP:8081/stream
对应代码里改成:
cpp
运行
const char* RPI_HOST = "192.168.x.x";
const int RPI_PORT = 8081;
const char* RSI_PATH = "/stream";
功能说明(完全兼容你原来的代码)
- 保留:WiFi 配网、自动重连、长按 BOOT 重置 WiFi、屏幕显示
- 新增:WiFi 连接成功后自动显示树莓派摄像头实时画面
- 画面:240x180 自动适配屏幕
- 自动重连:摄像头断开会自动重连
效果
- 开机 → 连 WiFi
- 显示设备信息 2 秒
- 自动连接树莓派摄像头
- 屏幕实时显示摄像头画面
- 流畅、稳定、不卡顿