作者/圖片提供:曹永忠/王景穗
在智慧家庭中,最重要的是家庭的安全與防護,而目前能夠提供安全與防護的方式大部分都選擇安裝視訊監控鏡頭,所以本文要介紹一個物廉價美的一個整合晶片:ESP32 CAM視訊整合模組,這是安可信(AI Thinker)為 ESP32 推出了ESP32 CAM視訊整合模組,本模組目前網路賣場市價約為新台幣二百元到三百元之間,本身附帶一個結合了 OmniVision 的 OV2640 影像感測器模組,基本像數為640480,加上MicroSD 記憶卡卡槽,大小約為2740mm,只要MicroUsb 5V供電,就可以運作,使其適合做為網路攝影機應用。

ESP32 CAM視訊整合模組
簡易主機監控平台架構
筆者為了簡化視訊簡易主機監控平台複雜性,如下圖所示,提出一個架構,筆者使用一個到多個ESP32 CAM視訊整合模組,並且使用NodeMCU-32S Lua WiFi 物聯網開發板,撰寫對應的韌體,使NodeMCU-32S Lua WiFi 成為一個無線基地台,與視訊簡易主機監控網站(網頁伺服器)相連,使用者可以透過無線網路連上其熱點,就可以觀看視訊簡易主機監控網站,就可以看到一個到多個ESP32 CAM視訊整合模組捕抓到的動態視訊。

視訊簡易主機監控平台程式結果畫面
ESP32 CAM視訊整合模組
對於ESP32 CAM視訊整合模組,筆者參閱陸向陽老師的【好物開箱】為低階網路攝影而生的ESP32-CAM一文,可以看到ESP32-CAM硬體功能配置圖,該模組配備OmniVision 的 OV2640 影像感測器模組,基本像數為640*480,為了可以儲存攝影、拍照等資料,又加上MicroSD 記憶卡卡槽,可以插入達4G MicroSD 記憶卡,為了視訊,也加入了PSRAM(Pseudo SRAM)加快整個處理的速度,並且在下方加入內置的高亮度的Flash Lamp,宛如一個功能完整的監控視訊攝影機(陸向陽, 2019)。

ESP32-CAM硬體功能配置圖(圖片來源)
ESP32 CAM視訊整合模組主要功能模組為下:
- 最小的 802.11b / g / n Wi-Fi BT SoC 模塊
- 低功耗 32 位 CPU,也可以為應用處理器服務
- 高達 160MHz 的時鐘速度,總結計算能力高達 600 DMIPS
- 內置 520 KB SRAM,外部 4MPSRAM
- 支持 UART / SPI / I2C / PWM / ADC / DAC
- 支持 OV2640 和 OV7670 攝像頭,內置閃光燈。
- 支持圖像 Wi-Fi 上傳
- 支持 TF 卡
- 支持多種睡眠模式。
- 嵌入式 Lwip 和 FreeRTOS
- 支持 STA / AP / STA + AP 操作模式
- 支持 Smart Config / AirKiss 技術
- 支持串列埠本地和遠程韌體升級(FOTA)
(資料來源:SeeedStudio官網)
如下表所示,為ESP32 CAM視訊整合模組整體規格:

(資料來源: SeeedStudio官網)
ESP32 CAM視訊整合模組開發準備
筆者為了簡化,如下圖所示,購買了ESP32 CAM視訊整合模組與燒錄底座,方便開發整體的系統。

ESP32 CAM與燒錄模組
筆者將ESP32 CAM視訊整合模組至於燒錄底座之上,如下圖所示就完成下圖之視訊攝影機。

整合燒錄模組之ESP32 CAM
ESP32 CAM視訊系統開發
我們遵照前幾章所述,將ESP32 CAM視訊整合模組的驅動程式安裝好之後,我們打開ESP 32開發板的開發工具:Sketch IDE整合開發軟體(安裝Arduino開發環境,請參考『ESP32程式設計(基礎篇):ESP32 IOT Programming (Basic Concept & Tricks)』之『Arduino開發IDE安裝』(曹永忠, 2020a, 2020b, 2020f),安裝ESP 32開發板 SDK請參考『ESP32程式設計(基礎篇):ESP32 IOT Programming (Basic Concept & Tricks)』之『安裝ESP32 Arduino 整合開發環境』(曹永忠, 2020a, 2020b, 2020c, 2020d, 2020e),編寫一段程式,編寫一段程式,並透過開發工具將整個程式編譯後,燒錄後上傳到ESP32 CAM視訊整合模組,進行程式測試。
ESP32CAM視訊鏡頭程式(ESPCAMV20210530.ino)
#include "initPins.h"
#include "esp_camera.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 network credentials
#define PART_BOUNDARY "123456789000000000000987654321"
// This project was tested with the AI Thinker Model, M5STACK PSRAM Model and M5STACK WITHOUT PSRAM
#define CAMERA_MODEL_AI_THINKER
//#define CAMERA_MODEL_M5STACK_PSRAM
//#define CAMERA_MODEL_M5STACK_WITHOUT_PSRAM
// Not tested with this model
//#define CAMERA_MODEL_WROVER_KIT
#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_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_WITHOUT_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 17
#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
#else
#error "Camera model not selected"
#endif
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;
//-----------------
const char* ssid = "CAMAP";
const char* password = "12345678";
// 設定ESP32固定IP的位置
IPAddress local_IP(192, 168, 4, 100);
// Set your Gateway IP address
IPAddress gateway(192, 168, 4, 1);
IPAddress subnet(255, 255, 0, 0);
IPAddress primaryDNS(8, 8, 8, 8); //optional
IPAddress secondaryDNS(8, 8, 4, 4); //optional
//--------------
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];
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 {
if(fb->width > 400){
if(fb->format != PIXFORMAT_JPEG){
bool jpeg_converted = frame2jpg(fb, 80, &_jpg_buf, &_jpg_buf_len);
esp_camera_fb_return(fb);
fb = NULL;
if(!jpeg_converted){
Serial.println("JPEG compression failed");
res = ESP_FAIL;
}
} else {
_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;
}
Serial.printf("MJPG: %uB\n",(uint32_t)(_jpg_buf_len));
}
return res;
}
void startCameraServer(){
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.server_port = 10000;
// config.server_port = 10002
;
// map port to Web server
httpd_uri_t index_uri = {
.uri = "/",
.method = HTTP_GET,
.handler = stream_handler,
.user_ctx = NULL
};
Serial.printf("Starting web server on port: '%d'\n", config.server_port);
if (httpd_start(&stream_httpd, &config) == ESP_OK) {
httpd_register_uri_handler(stream_httpd, &index_uri);
}
}
void setup() {
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector
Serial.begin(115200);
Serial.setDebugOutput(false);
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;
if(psramFound()){
config.frame_size = FRAMESIZE_SVGA;
config.jpeg_quality = 12;
config.fb_count = 2;
} else {
config.frame_size = FRAMESIZE_SVGA;
config.jpeg_quality = 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;
}
// Wi-Fi connection
// Configures static IP address
if (!WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS)) {
Serial.println("STA Failed to configure");
}
// Connect to Wi-Fi network with SSID and password
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
// Print local IP address and start web server
Serial.println("");
Serial.println("WiFi connected.");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
// Start streaming web server
startCameraServer();
}
void loop() {
delay(1);
}
ESP32CAM視訊鏡頭程式(initPins.h)
ESP32CAM視訊鏡頭程式(initPins.h)
#include
#include
WiFiMulti wifiMulti;
IPAddress ip ;
String ipdata ;
String apname ;
String MacData ;
//--------------
long POW(long num, int expo)
{
long tmp =1 ;
if (expo > 0)
{
for(int i = 0 ; i< expo ; i++)
tmp = tmp * num ;
return tmp ;
}
else
{
return tmp ;
}
}
String SPACE(int sp)
{
String tmp = "" ;
for (int i = 0 ; i < sp; i++)
{
tmp.concat(' ') ;
}
return tmp ;
}
String strzero(long num, int len, int base)
{
String retstring = String("");
int ln = 1 ;
int i = 0 ;
char tmp[10] ;
long tmpnum = num ;
int tmpchr = 0 ;
char hexcode[]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'} ;
while (ln <= len) { tmpchr = (int)(tmpnum % base) ; tmp[ln-1] = hexcode[tmpchr] ; ln++ ; tmpnum = (long)(tmpnum/base) ; } for (i = len-1; i >= 0 ; i --)
{
retstring.concat(tmp[i]);
}
return retstring;
}
unsigned long unstrzero(String hexstr, int base)
{
String chkstring ;
int len = hexstr.length() ;
unsigned int i = 0 ;
unsigned int tmp = 0 ;
unsigned int tmp1 = 0 ;
unsigned long tmpnum = 0 ;
String hexcode = String("0123456789ABCDEF") ;
for (i = 0 ; i < (len ) ; i++) { // chkstring= hexstr.substring(i,i) ; hexstr.toUpperCase() ; tmp = hexstr.charAt(i) ; // give i th char and return this char tmp1 = hexcode.indexOf(tmp) ; tmpnum = tmpnum + tmp1* POW(base,(len -i -1) ) ; } return tmpnum; } String print2HEX(int number) { String ttt ; if (number >= 0 && number < 16)
{
ttt = String("0") + String(number,HEX);
}
else
{
ttt = String(number,HEX);
}
return ttt ;
}
String GetMacAddress() {
// the MAC address of your WiFi shield
String Tmp = "" ;
byte mac[6];
// print your MAC address:
WiFi.macAddress(mac);
for (int i=0; i<6; i++)
{
Tmp.concat(print2HEX(mac[i])) ;
}
Tmp.toUpperCase() ;
return Tmp ;
}
String IpAddress2String(const IPAddress& ipAddress)
{
return String(ipAddress[0]) + String(".") +\
String(ipAddress[1]) + String(".") +\
String(ipAddress[2]) + String(".") +\
String(ipAddress[3]) ;
}
String chrtoString(char *p)
{
String tmp ;
char c ;
int count = 0 ;
while (count <100)
{
c= *p ;
if (c != 0x00)
{
tmp.concat(String(c)) ;
}
else
{
return tmp ;
}
count++ ;
p++;
}
}
void CopyString2Char(String ss, char *p)
{
// sprintf(p,"%s",ss) ;
if (ss.length() <=0)
{
*p = 0x00 ;
return ;
}
ss.toCharArray(p, ss.length()+1) ;
// *(p+ss.length()+1) = 0x00 ;
}
boolean CharCompare(char *p, char *q)
{
boolean flag = false ;
int count = 0 ;
int nomatch = 0 ;
while (flag <100) { if (*(p+count) == 0x00 or *(q+count) == 0x00) break ; if (*(p+count) != *(q+count) ) { nomatch ++ ; } count++ ; } if (nomatch >0)
{
return false ;
}
else
{
return true ;
}
}
void ShowAP()
{
Serial.print("Access Point:") ;
Serial.print(apname) ;
Serial.print("\n") ;
}
void ShowMAC()
{
Serial.print("MAC:") ;
Serial.print(MacData) ;
Serial.print("\n") ;
}
void ShowIP()
{
Serial.print("IP:") ;
Serial.print(ipdata) ;
Serial.print("\n") ;
}
void ShowInternet()
{
ShowMAC() ;
ShowAP() ;
ShowIP() ;
}
共同作者介紹
王景穗(Wang-Jing Suei)
國立高雄大學(NUK)電機工程學系碩士,碩士論文主要專研物聯網技術與微處理器應用整合,並協助學校針對實作性課程的遠距教學系統開發,結合自動控制系統與物聯網技術,設計開發教育性遠距操作系統。
(責任編輯:謝涵如)
- 【大氣監控站台開發案例(下)】環境監控即時監控看板 - 2021/11/25
- 【大氣監控站台開發案例(中)】環境監控雲端平台系統介紹 - 2021/11/02
- 【大氣監控站台開發案例(上)】 大氣監控站建置實例介紹 - 2021/10/18
訂閱MakerPRO知識充電報
與40000位開發者一同掌握科技創新的技術資訊!