ESP32 S3-CAM图传模块+MQTT+华为云端实现云端更新图片数据(Arduino IDE开发环境)
这次探索的意图是想给我的家庭监控系统做一个图传功能,当家里发生火灾或者可燃气体超标的时候,可以通过这个功能获取家里的实时画面,画面不需要流畅的视频,能间隔一定时间获取单帧画面即可,所以才有了以下文章。整个系统的其他功能另有一块STM32F103C8T6主芯片开发板板负责所有图传以外的功能,这块ESP32-S3-CAM模块基本算是完全独立于主芯片。
一、物料准备
硬件:
- ESP32 S3-CAM(乐鑫)
- type-c数据线
软件:
- 华为云端MQTT服务器
- Arduino IDE
二、调试过程
总体思路,这次购买的模块,只能产生RGB565格式 ,这是一种红5绿6蓝5的双字节原始格式,而app端需要的是base64格式的jpeg图片 ,这种格式在web端可以直接显示。而mqtt传输中最好使用base64格式,所以最终方案,是将RGB565压缩为jepg格式,再将jpeg格式转化为base64格式,再传输至云端。
转化途中自然需要大量缓存空间,我买的这个图传模块刚好有内部520KB+外部8MB的PSRAM,完全可以满足要求,整个过程可以简化为以下步骤:
- 配置Arduino开发环境
- 配置芯片引脚数据
- 相机初始化
- 连接wifi
- 连接MQTT云服务器(使用PubSubClient库)
- 循环获取画面并向服务器传送
- 摄像头拍照获取RGB565(双字节原始数据)
- 转化为JPEG(内置frame2jpg)
- 转化为base64(使用base64库)
- 组装MATT的JSON数据(使用ArduinoJson库)
- 传输至云端
以下提几个重点,完整代码贴在文章最后:
-
配置Arduino开发环境
根据下图配置,配置可以根据taobao商家给的开发说明去配,注意这一步千万不能省略而且要配仔细,我本人就是因为PSRAM配置项没有开启,导致代码中的PSRAM初始化一直没有成功,卡了我两小时


-
配置芯片引脚数据
这也可以从taobao给的示例代码中获取,省去手写的繁琐
c// 选择相机型号 #define CAMERA_MODEL_ESP32S3_EYE #if defined(CAMERA_MODEL_WROVER_KIT) #define PWDN_GPIO_NUM -1 #define RESET_GPIO_NUM -1 #define XCLK_GPIO_NUM 21 #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 19 #define Y4_GPIO_NUM 18 #define Y3_GPIO_NUM 5 #define Y2_GPIO_NUM 4 #define VSYNC_GPIO_NUM 25 #define HREF_GPIO_NUM 23 #define PCLK_GPIO_NUM 22 #elif defined(CAMERA_MODEL_ESP_EYE) #define PWDN_GPIO_NUM -1 #define RESET_GPIO_NUM -1 #define XCLK_GPIO_NUM 4 #define SIOD_GPIO_NUM 18 #define SIOC_GPIO_NUM 23 #define Y9_GPIO_NUM 36 #define Y8_GPIO_NUM 37 #define Y7_GPIO_NUM 38 #define Y6_GPIO_NUM 39 #define Y5_GPIO_NUM 35 #define Y4_GPIO_NUM 14 #define Y3_GPIO_NUM 13 #define Y2_GPIO_NUM 34 #define VSYNC_GPIO_NUM 5 #define HREF_GPIO_NUM 27 #define PCLK_GPIO_NUM 25 #define LED_GPIO_NUM 22 #elif defined(CAMERA_MODEL_M5STACK_PSRAM) #define PWDN_GPIO_NUM -1 #define RESET_GPIO_NUM 15 #define XCLK_GPIO_NUM 27 #define SIOD_GPIO_NUM 25 #define SIOC_GPIO_NUM 23 #define Y9_GPIO_NUM 19 #define Y8_GPIO_NUM 36 #define Y7_GPIO_NUM 18 #define Y6_GPIO_NUM 39 #define Y5_GPIO_NUM 5 #define Y4_GPIO_NUM 34 #define Y3_GPIO_NUM 35 #define Y2_GPIO_NUM 32 #define VSYNC_GPIO_NUM 22 #define HREF_GPIO_NUM 26 #define PCLK_GPIO_NUM 21 #elif defined(CAMERA_MODEL_M5STACK_V2_PSRAM) #define PWDN_GPIO_NUM -1 #define RESET_GPIO_NUM 15 #define XCLK_GPIO_NUM 27 #define SIOD_GPIO_NUM 22 #define SIOC_GPIO_NUM 23 #define Y9_GPIO_NUM 19 #define Y8_GPIO_NUM 36 #define Y7_GPIO_NUM 18 #define Y6_GPIO_NUM 39 #define Y5_GPIO_NUM 5 #define Y4_GPIO_NUM 34 #define Y3_GPIO_NUM 35 #define Y2_GPIO_NUM 32 #define VSYNC_GPIO_NUM 25 #define HREF_GPIO_NUM 26 #define PCLK_GPIO_NUM 21 #elif defined(CAMERA_MODEL_M5STACK_WIDE) #define PWDN_GPIO_NUM -1 #define RESET_GPIO_NUM 15 #define XCLK_GPIO_NUM 27 #define SIOD_GPIO_NUM 22 #define SIOC_GPIO_NUM 23 #define Y9_GPIO_NUM 19 #define Y8_GPIO_NUM 36 #define Y7_GPIO_NUM 18 #define Y6_GPIO_NUM 39 #define Y5_GPIO_NUM 5 #define Y4_GPIO_NUM 34 #define Y3_GPIO_NUM 35 #define Y2_GPIO_NUM 32 #define VSYNC_GPIO_NUM 25 #define HREF_GPIO_NUM 26 #define PCLK_GPIO_NUM 21 #define LED_GPIO_NUM 2 #elif defined(CAMERA_MODEL_M5STACK_ESP32CAM) #define PWDN_GPIO_NUM -1 #define RESET_GPIO_NUM 15 #define XCLK_GPIO_NUM 27 #define SIOD_GPIO_NUM 25 #define SIOC_GPIO_NUM 23 #define Y9_GPIO_NUM 19 #define Y8_GPIO_NUM 36 #define Y7_GPIO_NUM 18 #define Y6_GPIO_NUM 39 #define Y5_GPIO_NUM 5 #define Y4_GPIO_NUM 34 #define Y3_GPIO_NUM 35 #define Y2_GPIO_NUM 17 #define VSYNC_GPIO_NUM 22 #define HREF_GPIO_NUM 26 #define PCLK_GPIO_NUM 21 #elif defined(CAMERA_MODEL_M5STACK_UNITCAM) #define PWDN_GPIO_NUM -1 #define RESET_GPIO_NUM 15 #define XCLK_GPIO_NUM 27 #define SIOD_GPIO_NUM 25 #define SIOC_GPIO_NUM 23 #define Y9_GPIO_NUM 19 #define Y8_GPIO_NUM 36 #define Y7_GPIO_NUM 18 #define Y6_GPIO_NUM 39 #define Y5_GPIO_NUM 5 #define Y4_GPIO_NUM 34 #define Y3_GPIO_NUM 35 #define Y2_GPIO_NUM 32 #define VSYNC_GPIO_NUM 22 #define HREF_GPIO_NUM 26 #define PCLK_GPIO_NUM 21 #elif defined(CAMERA_MODEL_AI_THINKER) #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 // 4 for flash led or 33 for normal led #define LED_GPIO_NUM 4 #elif defined(CAMERA_MODEL_TTGO_T_JOURNAL) #define PWDN_GPIO_NUM 0 #define RESET_GPIO_NUM 15 #define XCLK_GPIO_NUM 27 #define SIOD_GPIO_NUM 25 #define SIOC_GPIO_NUM 23 #define Y9_GPIO_NUM 19 #define Y8_GPIO_NUM 36 #define Y7_GPIO_NUM 18 #define Y6_GPIO_NUM 39 #define Y5_GPIO_NUM 5 #define Y4_GPIO_NUM 34 #define Y3_GPIO_NUM 35 #define Y2_GPIO_NUM 17 #define VSYNC_GPIO_NUM 22 #define HREF_GPIO_NUM 26 #define PCLK_GPIO_NUM 21 #elif defined(CAMERA_MODEL_XIAO_ESP32S3) #define PWDN_GPIO_NUM -1 #define RESET_GPIO_NUM -1 #define XCLK_GPIO_NUM 10 #define SIOD_GPIO_NUM 40 #define SIOC_GPIO_NUM 39 #define Y9_GPIO_NUM 48 #define Y8_GPIO_NUM 11 #define Y7_GPIO_NUM 12 #define Y6_GPIO_NUM 14 #define Y5_GPIO_NUM 16 #define Y4_GPIO_NUM 18 #define Y3_GPIO_NUM 17 #define Y2_GPIO_NUM 15 #define VSYNC_GPIO_NUM 38 #define HREF_GPIO_NUM 47 #define PCLK_GPIO_NUM 13 #elif defined(CAMERA_MODEL_ESP32_CAM_BOARD) // The 18 pin header on the board has Y5 and Y3 swapped #define USE_BOARD_HEADER 0 #define PWDN_GPIO_NUM 32 #define RESET_GPIO_NUM 33 #define XCLK_GPIO_NUM 4 #define SIOD_GPIO_NUM 18 #define SIOC_GPIO_NUM 23 #define Y9_GPIO_NUM 36 #define Y8_GPIO_NUM 19 #define Y7_GPIO_NUM 21 #define Y6_GPIO_NUM 39 #if USE_BOARD_HEADER #define Y5_GPIO_NUM 13 #else #define Y5_GPIO_NUM 35 #endif #define Y4_GPIO_NUM 14 #if USE_BOARD_HEADER #define Y3_GPIO_NUM 35 #else #define Y3_GPIO_NUM 13 #endif #define Y2_GPIO_NUM 34 #define VSYNC_GPIO_NUM 5 #define HREF_GPIO_NUM 27 #define PCLK_GPIO_NUM 25 #elif defined(CAMERA_MODEL_ESP32S3_CAM_LCD) #define PWDN_GPIO_NUM -1 #define RESET_GPIO_NUM -1 #define XCLK_GPIO_NUM 40 #define SIOD_GPIO_NUM 17 #define SIOC_GPIO_NUM 18 #define Y9_GPIO_NUM 39 #define Y8_GPIO_NUM 41 #define Y7_GPIO_NUM 42 #define Y6_GPIO_NUM 12 #define Y5_GPIO_NUM 3 #define Y4_GPIO_NUM 14 #define Y3_GPIO_NUM 47 #define Y2_GPIO_NUM 13 #define VSYNC_GPIO_NUM 21 #define HREF_GPIO_NUM 38 #define PCLK_GPIO_NUM 11 #elif defined(CAMERA_MODEL_ESP32S2_CAM_BOARD) // The 18 pin header on the board has Y5 and Y3 swapped #define USE_BOARD_HEADER 0 #define PWDN_GPIO_NUM 1 #define RESET_GPIO_NUM 2 #define XCLK_GPIO_NUM 42 #define SIOD_GPIO_NUM 41 #define SIOC_GPIO_NUM 18 #define Y9_GPIO_NUM 16 #define Y8_GPIO_NUM 39 #define Y7_GPIO_NUM 40 #define Y6_GPIO_NUM 15 #if USE_BOARD_HEADER #define Y5_GPIO_NUM 12 #else #define Y5_GPIO_NUM 13 #endif #define Y4_GPIO_NUM 5 #if USE_BOARD_HEADER #define Y3_GPIO_NUM 13 #else #define Y3_GPIO_NUM 12 #endif #define Y2_GPIO_NUM 14 #define VSYNC_GPIO_NUM 38 #define HREF_GPIO_NUM 4 #define PCLK_GPIO_NUM 3 #elif defined(CAMERA_MODEL_ESP32S3_EYE) #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 #elif defined(CAMERA_MODEL_DFRobot_FireBeetle2_ESP32S3) || defined(CAMERA_MODEL_DFRobot_Romeo_ESP32S3) #define PWDN_GPIO_NUM -1 #define RESET_GPIO_NUM -1 #define XCLK_GPIO_NUM 45 #define SIOD_GPIO_NUM 1 #define SIOC_GPIO_NUM 2 #define Y9_GPIO_NUM 48 #define Y8_GPIO_NUM 46 #define Y7_GPIO_NUM 8 #define Y6_GPIO_NUM 7 #define Y5_GPIO_NUM 4 #define Y4_GPIO_NUM 41 #define Y3_GPIO_NUM 40 #define Y2_GPIO_NUM 39 #define VSYNC_GPIO_NUM 6 #define HREF_GPIO_NUM 42 #define PCLK_GPIO_NUM 5 #else #error "Camera model not selected" #endif
完整代码:
c
#include "WiFi.h"
#include "esp_camera.h"
#include "esp_timer.h"
#include "img_converters.h"
#include "Arduino.h"
#include "soc/soc.h" // Disable brownour problems
#include "soc/rtc_cntl_reg.h" // Disable brownour problems
#include "driver/rtc_io.h"
#include "camera_pins.h"
#include <PubSubClient.h>
#include "base64.hpp"
#include <libb64/cencode.h>
#include <ArduinoJson.h>
// Wifi Info
const char* ssid = "wifi-username";
const char* password = "wifi-password";
// MQTT Info
const char* client_id = "monitor1_0_0_2025080613";
const char* mqtt_server = "99dc6497df.st1.iotda-device.cn-north-4.myhuaweicloud.com";
const int mqtt_port = 1883;
const char* mqtt_user = "mqtt_user";
const char* mqtt_password = "mqtt_password";
// Topic Info
const char* mqtt_TopicName = "$oc/devices/{device_id}/sys/properties/report";
WiFiClient mqttClient;
PubSubClient client(mqttClient);
void setup_camera() {
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 = 15000000;
/* @param frame_size One of
* - FRAMESIZE_96X96, // 96x96
* - FRAMESIZE_QQVGA, // 160x120
* - FRAMESIZE_QCIF, // 176x144
* - FRAMESIZE_HQVGA, // 240x176
* - FRAMESIZE_240X240, // 240x240
* - FRAMESIZE_QVGA, // 320x240
* - FRAMESIZE_CIF, // 400x296
* - FRAMESIZE_HVGA, // 480x320
* - FRAMESIZE_VGA, // 640x480
* - FRAMESIZE_SVGA, // 800x600
* - FRAMESIZE_XGA, // 1024x768
* - FRAMESIZE_HD, // 1280x720
* - FRAMESIZE_SXGA, // 1280x1024
* - FRAMESIZE_UXGA, // 1600x1200
* - FRAMESIZE_FHD, // 1920x1080
* - FRAMESIZE_P_HD, // 720x1280
* - FRAMESIZE_P_3MP, // 864x1536
* - FRAMESIZE_QXGA, // 2048x1536
* - FRAMESIZE_QHD, // 2560x1440
* - FRAMESIZE_WQXGA, // 2560x1600
* - FRAMESIZE_P_FHD, // 1080x1920
* - FRAMESIZE_QSXGA, // 2560x1920
*/
config.frame_size = FRAMESIZE_SVGA;
config.pixel_format = PIXFORMAT_RGB565; // for streaming
config.fb_location = CAMERA_FB_IN_PSRAM;
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;
} else {
Serial.printf("Camera init succeed");
}
}
//拍照发送到mqtt
void getimg() {
// 1. 获取RGB565图像
camera_fb_t* rgb565_fb = esp_camera_fb_get();
if (!rgb565_fb) {
Serial.println("Get RGB565 img failed!\n");
return;
}
// 2. 转换为JPEG
uint8_t* jpg_buf = NULL; // 存储转换后的 JPEG 数据
size_t jpg_len = 0; // 存储 JPEG 数据长度
bool success = frame2jpg(rgb565_fb, 50, &jpg_buf, &jpg_len); // 第二个参数是 JPEG 质量(0-100,数值越大质量越好)
esp_camera_fb_return(rgb565_fb); // 释放原始 RGB565 帧缓冲区(转换后不再需要)
if (!success) {
Serial.println("RGB565 to JPEG failed\n");
free(jpg_buf);
return;
}
Serial.printf("frame to jpeg succeed,JPEG length:%d byte\r\n", jpg_len);
// 3. 计算 Base64 输出缓冲区大小
size_t base64_buf_size = ((jpg_len + 2) / 3) * 4 + 1; // +1 确保有空间放 null 终止符
char* base64_buf = (char*)malloc(base64_buf_size);
if (!base64_buf) {
Serial.println("Base64 malloc failed!\n");
free(jpg_buf); // 释放 JPEG 缓冲区
return;
}
// 4. 使用库函数 encode_base64() 进行编码,函数参数:(输入数据, 输入长度, 输出缓冲区) ,库会自动添加 null 终止符,因此 base64_buf 可直接作为字符串使用
size_t base64_len = encode_base64(jpg_buf, jpg_len, (unsigned char*)base64_buf);
Serial.printf("Base64 encode succeed,length: %dbyte\r\n", base64_len);
// 5. 构造MQTT JSON结构
JsonDocument doc;
JsonArray services = doc["services"].to<JsonArray>();
JsonObject service = services.add<JsonObject>();
service["service_id"] = "Monitor"; // 设置service_id
JsonObject properties = service["properties"].to<JsonObject>();
properties["img"] = base64_buf; // 传入Base64图像数据
String jsonString;
serializeJson(doc, jsonString);
// 6.直接发送出去
if (client.publish(mqtt_TopicName, jsonString.c_str())) {
Serial.printf("send succeed,JPEG lengh: %dbyte,Base64 length: %dbyte\r\n",
jpg_len, base64_len);
} else {
Serial.println("MQTT send failed\r\n");
}
// 7. 释放 JPEG 缓冲区,释放 Base64 缓冲区(必须!)
free(jpg_buf);
free(base64_buf);
}
void setup_wifi() {
delay(10);
// 连接WIFI
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
Serial.print("IP address : ");
Serial.println(WiFi.localIP());
}
//MQTT重连
void reconnect() {
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
if (client.connect(client_id, mqtt_user, mqtt_password)) {
Serial.println("connected");
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
delay(5000);
}
}
}
void setup() {
Serial.begin(115200);
Serial.setDebugOutput(true);
Serial.println();
// 启用PSRAM(必须在相机初始化前执行)
if (psramFound()) {
Serial.printf("PSRAM :%d bytes\r\n", ESP.getPsramSize()); // 应输出8388608字节(8MB)
} else {
Serial.println("PSRAM init failed!\r\n");
}
setup_camera(); //设置相机
setup_wifi(); //连接WIFI
client.setServer(mqtt_server, mqtt_port); //连接MQTT Broker
client.setKeepAlive(60); // 强制设置为60秒,
client.setBufferSize(65535);
if (client.connect(client_id, mqtt_user, mqtt_password)) {
Serial.println("mqtt connected");
}
}
void loop() {
getimg();
if (!client.connected()) {
reconnect();
}
delay(3000);
}