2020年8月22日 星期六

[ESP32] 用 ESPAsyncWebServer 建立你的第一個網頁

本文是參考「硬派筆記  用 ESPAsyncWebServer 建立你的第一個網頁」,在實做過程中的一些補充。

本文適用於 ESP 系列開發板,包括 ESP8266/ESP32/ESP32-Cam。


將硬派筆記內的程式碼上傳到板子時,可能會出現找不到 AsyncTCP.h 和 ESPAsyncWebServer.h 等函式庫,可以到此下載

https://github.com/me-no-dev


由於是把 ESP32 開發板當作 AP,因此在程式上傳完畢後,需要中斷您的網路連線,並重新連線到 ESP32 所命名的網路「ESP_WiFi」。


鍵入密碼「12345678」。


然後在您的電腦瀏覽器內鍵入網址「192.168.4.1」。

隨後再鍵入「192.168.4.1/test」。

演練成功。

2020年8月16日 星期日

[ESP32-Cam] BLE(Bluetooth Low Energy) 低功耗藍牙的使用

本文旨在說明如何用手機透過藍牙跟 ESP32-Cam 作溝通,看完之後您將會發現這將是一件非常簡單的事。

ESP32-Cam 開發板搭載了一個低功耗藍牙,它跟一般的 HC-05、HC-06 藍牙有點兒不同,這方面您可以參考一下維基百科,在「無線電介面」章節裡有詳細介紹他們的區別。

https://zh.wikipedia.org/wiki/%E8%93%9D%E7%89%99%E4%BD%8E%E5%8A%9F%E8%80%97#%E6%97%A0%E7%BA%BF%E7%94%B5%E6%8E%A5%E5%8F%A3


ESP32-Cam 發送訊息給手機

以下這個程式會讓 ESP32-Cam 每隔 1 秒鐘發送一次 "Hello World!" 字串給手機。

#include <BluetoothSerial.h>

BluetoothSerial BT;  // 宣告藍芽物件,名稱為BT


void setup() {

  Serial.begin(115200);

  BT.begin("RobotWolfGroup");  // 指定藍芽裝置的名稱

}


void loop() {

  BT.println("Hello World!");

  delay(1000);

您有沒有發現程式碼才短短幾行? 

是的,免設定密碼、鮑率...等有的沒的,它就是這麼簡單好用。


藍牙配對

在 ESP32-Cam 端程式上傳完畢後,接下來需要用手機跟它的藍牙配對。

Step1 打開手機藍牙,選擇「配對新裝置」。

Step2 找到「RobotWolfGroup」這個藍牙裝置後,點擊它並配對。這個 BLE 在配對時是不需要輸入密碼的。


手機安裝 APP

我們需要在手機裡安裝 APP,才可以接收來自 BLE 的訊息。

Step1 請打開手機的「PLAY商店」,在裡面搜尋「Arduino Bluetooth」。


Step2 找到「Arduino Bluetooth Control」,安裝它。


Step3 安裝完成後,請開啟它。開啟後如下圖


Step4 點選畫面右上角落的圓形雙箭頭,搜尋「RobotWolfGroup」藍牙裝置,找到後點選它。


Step5 再點選畫面右上角落的「Terminal」,然後您就會看到如下畫面



手機發送訊息給 ESP32-Cam

以下這個程式可以讓手機發送訊息給 ESP32-Cam,請將該程式上傳到 ESP32-Cam。


#include <BluetoothSerial.h>

BluetoothSerial BT;//宣告藍芽物件,名稱為BT


void setup() {

  Serial.begin(115200);

  BT.begin("RobotWolfGroup");  //指定藍芽裝置的名稱

}


void loop() {

  //檢查藍芽內是否有資料

  while (BT.available()) {

    //讀取藍芽資料

    String str = BT.readString();

    //顯示在序列視窗

    Serial.println(str);

  }

  delay(10);

}


上傳完成後,請打開「序列埠監看視窗」。


開啟手機 APP 並完成選取藍牙裝置(如果之前有順利選取可用裝置,當再次啟動 APP 時它會自動連線」。


點選畫面右上角落的「Terminal」,然後在視窗最下方的「Type in data to send」欄位內輸入訊息,然後您就會看到如下畫面。


接著請您再檢查一下「序列埠監視視窗」裡是否有接收到訊息,如下圖



手機收發 ESP32-Cam 訊息

將上面兩個程式合併,就可以讓手機收發 ESP32-Cam 訊息。

這個程式是手機鍵入 "A",ESP32-Cam 會回應 "Apple";手機鍵入 "B",ESP32-Cam 會回應 "Banana";若手機鍵入 "A" 、"B" 以外的字,ESP32-Cam 會回應 "N/A"。

請將下方程式上傳到 ESP32-Cam。


#include <BluetoothSerial.h>

BluetoothSerial BT; // 宣告藍芽物件,名稱為BT


void setup() {

  Serial.begin(115200);

  BT.begin("RobotWolfGroup"); // 指定藍芽裝置的名稱

}


void loop() {

  // 檢查藍芽內是否有資料

  while (BT.available()) {

    // 讀取藍芽資料

    String str = BT.readString();

    // 顯示在序列視窗

    Serial.println(str);

    // 回訊給手機

    if (str == "A")

      BT.println("Apple");

    else if (str == "B")  

      BT.println("Banana");

    else 

      BT.println("N/A");  

  }

  delay(10);

}


開啟手機 APP 並完成選取藍牙裝置(如果之前有順利選取可用裝置,當再次啟動 APP 時它會自動連線」。


點選畫面右上角落的「Terminal」,然後在視窗最下方的「Type in data to send」欄位內輸入訊息,然後您就會看到如下畫面。


使用幾次之後,發現這訊息時間不對,而且訊息一去一回大約花了 2 秒鐘時間,速度還真是慢啊,應該是這個 APP 的問題吧?

不過這個 APP 還是有可取之處,它的語音控制功能 (Voice Control) 還不錯,可以設定 10 組的語音與相對應要發送的字串,這個用來控制家電倒是蠻不錯的,我用它來控制 5 種電器的開與關。


語音控制功能這部分跟 ESP32-Cam 無關,我們就不多介紹了,那就請您自行練習囉。



採購資訊

Micro SD 4G 記憶卡 https://www.ruten.com.tw/item/show?22033106394834

ESP32 開發指南(書) https://www.ruten.com.tw/item/show?22023657661505

ESP32-Cam 開發板 https://www.ruten.com.tw/item/show?22018501441929

ESP32-Cam 雙軸雲台(基本款)套件 https://www.ruten.com.tw/item/show?22024688065979

ESP32-Cam 雙軸雲台(人體偵測款)套件 https://www.ruten.com.tw/item/show?22024688076465




2020年8月14日 星期五

[ESP32-Cam] Micro SD 記憶卡的檔案操作

 ESP32-Cam 板載 Micro SD 記憶卡既然擁有可以存取檔案的功能,那麼如何對資料夾和檔案的建立/列示/修改/刪除/更名...等操作,就是我們必學的課題。

底下程式把這些課題都演練了一遍,您可以先把程式上傳到板子,然後打開「序列埠監視視窗」,仔細觀察整個程式運作過程。以下為您說明視窗內的訊息,請注意反白部分。

首先是列示 SD 卡的性質


列出根目錄下的所有資料夾和檔案


新建一個 <mydir> 資料夾 > 列示根目錄狀況 > 移除資料夾 > 再次列示根目錄狀況


新建一個叫 hello.txt 的檔案 > 將字串附加到檔案 > 讀取並印出檔案內容

我想有必要為上面的過程再詳加說明一下...

首先,它新建一個叫 hello.txt 的檔案 ,同時寫入 "Hello " 字串,

然後將字串 "World!\n" 附加到檔案裡,這個 "\n" 就是換行,

最後讀取並印出檔案內容 "Hello Wolrd!"。


訊息窗內都是一些簡單的英文,請您自行閱讀,我們的介紹就到這裡了。


程式碼

#include "FS.h" 

#include "SD_MMC.h" 


//List dir in SD card

void listDir(fs::FS &fs, const char * dirname, uint8_t levels){

    Serial.printf("Listing directory: %s\n", dirname);


    File root = fs.open(dirname);

    if(!root){

        Serial.println("Failed to open directory");

        return;

    }

    if(!root.isDirectory()){

        Serial.println("Not a directory");

        return;

    }


    File file = root.openNextFile();

    while(file){

        if(file.isDirectory()){

            Serial.print("  DIR : ");

            Serial.println(file.name());

            if(levels){

                listDir(fs, file.name(), levels -1);

            }

        } else {

            Serial.print("  FILE: ");

            Serial.print(file.name());

            Serial.print("  SIZE: ");

            Serial.println(file.size());

        }

        file = root.openNextFile();

    }

}


//Create a dir in SD card

void createDir(fs::FS &fs, const char * path){

    Serial.printf("Creating Dir: %s\n", path);

    if(fs.mkdir(path)){

        Serial.println("Dir created");

    } else {

        Serial.println("mkdir failed");

    }

}


//delete a dir in SD card

void removeDir(fs::FS &fs, const char * path){

    Serial.printf("Removing Dir: %s\n", path);

    if(fs.rmdir(path)){

        Serial.println("Dir removed");

    } else {

        Serial.println("rmdir failed");

    }

}


//Read a file in SD card

void readFile(fs::FS &fs, const char * path){

    Serial.printf("Reading file: %s\n", path);


    File file = fs.open(path);

    if(!file){

        Serial.println("Failed to open file for reading");

        return;

    }


    Serial.print("Read from file: ");

    while(file.available()){

        Serial.write(file.read());

    }

}


//Write a file in SD card

void writeFile(fs::FS &fs, const char * path, const char * message){

    Serial.printf("Writing file: %s\n", path);


    File file = fs.open(path, FILE_WRITE);

    if(!file){

        Serial.println("Failed to open file for writing");

        return;

    }

   

 

   //fwrite(fb->buf, 1, fb->len, file);

    if(file.print(message)){

        Serial.println("File written");

    } else {

        Serial.println("Write failed");

    }

}


//Append to the end of file in SD card

void appendFile(fs::FS &fs, const char * path, const char * message){

    Serial.printf("Appending to file: %s\n", path);


    File file = fs.open(path, FILE_APPEND);

    if(!file){

        Serial.println("Failed to open file for appending");

        return;

    }

    if(file.print(message)){

        Serial.println("Message appended");

    } else {

        Serial.println("Append failed");

    }

}


//Rename a file in SD card

void renameFile(fs::FS &fs, const char * path1, const char * path2){

    Serial.printf("Renaming file %s to %s\n", path1, path2);

    if (fs.rename(path1, path2)) {

        Serial.println("File renamed");

    } else {

        Serial.println("Rename failed");

    }

}


//Delete a file in SD card

void deleteFile(fs::FS &fs, const char * path){

    Serial.printf("Deleting file: %s\n", path);

    if(fs.remove(path)){

        Serial.println("File deleted");

    } else {

        Serial.println("Delete failed");

    }

}


//Test read and write speed using test.txt file

void testFileIO(fs::FS &fs, const char * path){

    File file = fs.open(path);

    static uint8_t buf[512];

    size_t len = 0;

    uint32_t start = millis();

    uint32_t end = start;

    if(file){

        len = file.size();

        size_t flen = len;

        start = millis();

        while(len){

            size_t toRead = len;

            if(toRead > 512){

                toRead = 512;

            }

            file.read(buf, toRead);

            len -= toRead;

        }

        end = millis() - start;

        Serial.printf("%u bytes read for %u ms\n", flen, end);

        file.close();

    } else {

        Serial.println("Failed to open file for reading");

    }



    file = fs.open(path, FILE_WRITE);

    if(!file){

        Serial.println("Failed to open file for writing");

        return;

    }


    size_t i;

    start = millis();

    for(i=0; i<2048; i++){

        file.write(buf, 512);

    }

    end = millis() - start;

    Serial.printf("%u bytes written for %u ms\n", 2048 * 512, end);

    file.close();

}



void setup() {  

  Serial.begin(115200);

  Serial.println("SDcard Testing....");


   if(!SD_MMC.begin()){

        Serial.println("Card Mount Failed");

        return;

    }

    uint8_t cardType = SD_MMC.cardType();


    if(cardType == CARD_NONE){

        Serial.println("No SD_MMC card attached");

        return;

    }


    Serial.print("SD_MMC Card Type: ");

    if(cardType == CARD_MMC){

        Serial.println("MMC");

    } else if(cardType == CARD_SD){

        Serial.println("SDSC");

    } else if(cardType == CARD_SDHC){

        Serial.println("SDHC");

    } else {

        Serial.println("UNKNOWN");

    }


    uint64_t cardSize = SD_MMC.cardSize() / (1024 * 1024);

    Serial.printf("SD_MMC Card Size: %lluMB\n", cardSize);


    listDir(SD_MMC, "/", 0);

    createDir(SD_MMC, "/mydir");

    listDir(SD_MMC, "/", 0);

    removeDir(SD_MMC, "/mydir");

    listDir(SD_MMC, "/", 2);

    writeFile(SD_MMC, "/hello.txt", "Hello ");

    appendFile(SD_MMC, "/hello.txt", "World!\n");

    readFile(SD_MMC, "/hello.txt");

    deleteFile(SD_MMC, "/foo.txt");

    renameFile(SD_MMC, "/hello.txt", "/foo.txt");

    readFile(SD_MMC, "/foo.txt");

    testFileIO(SD_MMC, "/test.txt");

    Serial.printf("Total space: %lluMB\n", SD_MMC.totalBytes() / (1024 * 1024));

    Serial.printf("Used space: %lluMB\n", SD_MMC.usedBytes() / (1024 * 1024));  

  

}


void loop() {

  // put your main code here, to run repeatedly:

  delay(10000);

}


採購資訊

Micro SD 4G 記憶卡 https://www.ruten.com.tw/item/show?22033106394834

ESP32 開發指南(書) https://www.ruten.com.tw/item/show?22023657661505

ESP32-Cam 開發板 https://www.ruten.com.tw/item/show?22018501441929

ESP32-Cam 雙軸雲台(基本款)套件 https://www.ruten.com.tw/item/show?22024688065979

ESP32-Cam 雙軸雲台(人體偵測款)套件 https://www.ruten.com.tw/item/show?22024688076465





2020年8月13日 星期四

[ESP32-Cam] 拍照並儲存到板載 Micro SD 記憶卡

 ESP32-Cam 板載 Micro SD 記憶卡插槽,可以用來儲存相片等資料,如果您要做大量的資料傳輸,也可以把它拿來當作緩衝區使用。

原廠文件顯示 ESP32-Cam 最大只能用到 4G 記憶卡,但有網友表示 16G 也可以。


格式化記憶卡

記憶卡在使用前必須先格式化,格式化的規格有 NTFS、FAT、FAT32。

有一些記憶卡在出廠前即已完成格式化,在您將它插入電腦後可以確認一下。如果尚未格式化或是您想重新格式化,可以依照下列步驟實施。

Step1 將記憶卡插入電腦,打開「我的電腦」或「檔案管理員」。

Step2 用滑鼠右鍵點擊「抽取式磁碟」,再選取「格式化...」,出現視窗



Step3 選項「檔案系統」選取「FAT32」,按一下「開始」按鈕。格式化完成後會顯示如下視窗


格式化好記憶卡後,接下來進入主題--拍照並儲存到記憶卡。

這個程式的流程如下

進入睡眠模式 > 按下[重置按鈕] 喚醒 > 拍照 > 儲存到記憶卡 > (回到最前頭的進入睡眠模式)

認識板載 Micro SD 記憶卡插槽

ESP32-Cam 使用數個 GPIO 腳位控制 Micro SD 記憶卡,如下圖左邊的腳位全用上了


根據上面的腳位圖和電路圖,我們整理出 ESP32-Cam 和 Micro SD 腳位關係表,如下

上述連接 Micro SD 的腳位,如果沒有在使用存取記憶卡功能時,可以設定輸出和輸入等 IO 功能。

同時,這些 GPIO 腳位都是 RTC,也都支援 ADC 功能。


程式碼

#include "esp_camera.h"

#include "Arduino.h"

#include "FS.h"                // SD Card ESP32

#include "SD_MMC.h"            // SD Card ESP32

#include "soc/soc.h"           // Disable brownour problems

#include "soc/rtc_cntl_reg.h"  // Disable brownour problems

#include "driver/rtc_io.h"

#include <EEPROM.h>            // read and write from flash memory


// define the number of bytes you want to access

#define EEPROM_SIZE 1


// Pin definition for 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


int pictureNumber = 0;


void setup() {

  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector

 

  Serial.begin(115200);

  //Serial.setDebugOutput(true);

  //Serial.println();

  

  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_UXGA; // FRAMESIZE_ + QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA

    config.jpeg_quality = 10;

    config.fb_count = 2;

  } else {

    config.frame_size = FRAMESIZE_SVGA;

    config.jpeg_quality = 12;

    config.fb_count = 1;

  }

  

  // Init Camera

  esp_err_t err = esp_camera_init(&config);

  if (err != ESP_OK) {

    Serial.printf("Camera init failed with error 0x%x", err);

    return;

  }

  

  //Serial.println("Starting SD Card");

  if(!SD_MMC.begin()){

    Serial.println("SD Card Mount Failed");

    return;

  }

  

  uint8_t cardType = SD_MMC.cardType();

  if(cardType == CARD_NONE){

    Serial.println("No SD Card attached");

    return;

  }

    

  camera_fb_t * fb = NULL;

  

  // Take Picture with Camera

  fb = esp_camera_fb_get();  

  if(!fb) {

    Serial.println("Camera capture failed");

    return;

  }

  // initialize EEPROM with predefined size

  EEPROM.begin(EEPROM_SIZE);

  pictureNumber = EEPROM.read(0) + 1;


  // Path where new picture will be saved in SD Card

  String path = "/picture" + String(pictureNumber) +".jpg";


  fs::FS &fs = SD_MMC; 

  Serial.printf("Picture file name: %s\n", path.c_str());

  

  File file = fs.open(path.c_str(), FILE_WRITE);

  if(!file){

    Serial.println("Failed to open file in writing mode");

  } 

  else {

    file.write(fb->buf, fb->len); // payload (image), payload length

    Serial.printf("Saved file to path: %s\n", path.c_str());

    EEPROM.write(0, pictureNumber);

    EEPROM.commit();

  }

  file.close();

  esp_camera_fb_return(fb); 

  

  // Turns off the ESP32-CAM white on-board LED (flash) connected to GPIO 4

  pinMode(4, OUTPUT);

  digitalWrite(4, LOW);

  rtc_gpio_hold_en(GPIO_NUM_4);

  

  delay(2000);

  Serial.println("Going to sleep now");

  delay(2000);

  esp_deep_sleep_start();

  Serial.println("This will never be printed");

}


void loop() {  

}


將上面的程式上傳到板子,

移除 GPIO0 和 Gnd 的接線,

打開「序列埠監視視窗」,

按一下板子上的 [Reset] 鍵,

您會發現閃光燈亮了一下,同時「序列埠監視視窗」顯示它已將一張照片儲存到記憶卡內。

然後呢?

它又進入睡眠模式,

除非您又按一下 [Reset] 鍵,

如此周而復始。


最後,我們用電腦檢查剛剛 ESP32-Cam 在記憶卡裡面儲存了哪些東西,如下

檔名是 picture???,後面數字是流水號。

檔案類型是 jpg

大小是 315KB


我查看了一下它的內容,如下


如果要作為長時間監控拍攝或縮時攝影,有必要把照片的尺寸和解析度縮小才行。


採購資訊

Micro SD 4G 記憶卡 https://www.ruten.com.tw/item/show?22033106394834

ESP32 開發指南(書) https://www.ruten.com.tw/item/show?22023657661505

ESP32-Cam 開發板 https://www.ruten.com.tw/item/show?22018501441929

ESP32-Cam 雙軸雲台(基本款)套件 https://www.ruten.com.tw/item/show?22024688065979

ESP32-Cam 雙軸雲台(人體偵測款)套件 https://www.ruten.com.tw/item/show?22024688076465


[ESP32-Cam] 讀取網際網路上的時間

 ESP 系列開發板因為可以連上網際網路,所以當專案或裝置需要精確的時間時,可以從網路上獲取目前的時間。


Step1 點擊下拉功能表 [檔案] > [範例] > [ESP] > [Time] > SimpleTime。出現視窗


Step2 請修改程式碼,填入正確的 ssid 和密碼,並上傳程式,成功後記得移除 GPIO0 和 Gnd 腳位上的接線。

Step3 打開「序列埠監控視窗」。出現視窗

您可以看到視窗內有顯示目前的日期和時間。


好的,我們透過網路讀取到時間了,但仔細看了一下,雖然日期是對的,時間卻是怪怪的,這是因為時區不正確。

想要解決時區不正確的問題,必須先瞭解程式碼,以下就針對有關的程式碼來說明。

const char* ntpServer = "pool.ntp.org";  // 讀取時間的伺服器網址

const long  gmtOffset_sec = 3600;  // GMT 時區的偏移量

const int   daylightOffset_sec = 3600;  // 日光節約時間的偏移量


根據以上資料,我們知道這個時間是去一個叫 "pool.ntp.org" 的網站取得的,它運作的方式如下圖


再來,我們搜尋到一個可以顯示 GMT 時間的網站,您可以開啟以下網頁並觀察一下時間

https://24timezones.com/time-zone/gmt#gref


然後,我們回過頭來把時區偏移量設為0,程式碼修改成

const long  gmtOffset_sec = 0;  // GMT 時區的偏移量

再重新上載程式,上載成功後再重新打開「序列埠監控視窗」。此時您看到的畫面應該類似下圖,只是時間不同。

這裡可以觀察到 GMT 網頁上的時間和「序列埠監控視窗」內的時間相差了 1 小時。而我的電腦時間是 13:12:43。

我並不想去理會為什麼相差 1 小時,我在意的是「序列埠監控視窗」內的時間比我的時間慢了 7 小時。而 7 小時等於 60*60*7=25200 秒。


我們回過頭來把時區偏移量設為 25200,程式碼修改成

const long  gmtOffset_sec = 25200;  // GMT 時區的偏移量

再重新上載程式,上載成功後再重新打開「序列埠監控視窗」。此時您可以檢查一下時間,應該是正確了。

在我們能夠掌握時間之後,就可以對 ESP32-Cam 做更多的應用,例如指定某一特定的時間開啟攝影鏡頭做監控...等等。


採購資訊

ESP32 開發指南(書) https://www.ruten.com.tw/item/show?22023657661505

ESP32-Cam 開發板 https://www.ruten.com.tw/item/show?22018501441929

ESP32-Cam 雙軸雲台(基本款)套件 https://www.ruten.com.tw/item/show?22024688065979

ESP32-Cam 雙軸雲台(人體偵測款)套件 https://www.ruten.com.tw/item/show?22024688076465





2020年8月12日 星期三

[ESP32-Cam] 閃光燈功能

 ESP32-Cam 開發板上有一只非常亮的 LED,它主要是作為攝影或拍照時補光用。這只 LED 是接在 GPIO4 腳位(詳下圖),因此當我們讓該腳位輸出高電位時就可點亮 LED。

程式碼

// Blink.ino for ESP32-Cam

int LED_BUILTIN = 4;

void setup() {

  Serial.begin(9600);

  // initialize digital pin LED_BUILTIN as an output.

  pinMode(LED_BUILTIN, OUTPUT);

}


void loop() {

  Serial.println("ON");

  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)

  delay(100);     // wait for a second

  Serial.println("OFF");

  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW

  delay(1000);                       // wait for a second

}

上傳上面程式後記得要移除 GPIO0 和 GND 的連接線,然後按一下 ESP32-Cam 板上的按鈕,接著您就可以看到 LED 一閃一閃。



如果您夠仔細,您應該有發現到 GPIO4 不只可以控制 LED,它同時還有其它功能。是的,您可以回頭去仔細看一下腳位圖上 GPIO4 腳位旁標註著 "HS2_DATA1 / Falsh",當 ESP32-Cam 存取 Micro SD 卡時,也會同時使用到 GPIO4 腳位並點亮 LED。

「存取 Micro SD 卡時,也會點亮 LED」這種情況可能不是我們樂見的,還好有辦法可以解決這種窘境,但這不在本文所討論的範圍內,在我們以後講到如何使用 Micro SD 卡時就會揭曉。


採購資訊

ESP32-Cam 開發板 https://www.ruten.com.tw/item/show?22018501441929

ESP32-Cam 雙軸雲台(基本款)套件 https://www.ruten.com.tw/item/show?22024688065979

ESP32-Cam 雙軸雲台(人體偵測款)套件 https://www.ruten.com.tw/item/show?22024688076465

2020年8月8日 星期六

[ESP32-Cam] 攝影功能

 ESP32-Cam 是一片 CP 值非常高的開發板,只要新台幣二百出頭就可以在你家建立一個監視系統,透過網路可作為居家監控、嬰兒照護、即時錄影...等非常實用的功能。


準備材料

1. ESP32-Cam 開發板 *1

2.USB2TTL 模組 *1

3.杜邦線母母頭 *5


電路接線

請依下列方式用杜邦線將 USB2TTL 模塊和 ESP32-Cam 開發板連接。

USB2 TTL           ESP32-Cam

5V                         5V

Gnd                       Gnd

RXD                      UOT

TXD                      UnR

在燒錄模式時還需把 ESP32-Cam 的 IO0 腳位接地,也就是

IO0  ----  Gnd


程式

如何在 Arduino IDE 裡使用 ESP 系列的開發板我們就不在此重複說明,如尚未明瞭的人可詳

https://pizgchen.blogspot.com/2019/08/esp32-arduino-ide-esp32.html


Step1 開啟 Arduino IDE,點擊下拉功能表 [檔案] > [範例] > [ESP32] > [Camera] > [CameraWebServer]。


Step2 修改程式內容,將以下這一行改成備註

define CAMERA_MODEL_WROVER_KIT

並將以下這一行移除備註

// #define CAMERA_MODEL_AI_THINKER

更改後如下所示

Step3 在這裡填入你家 IP 分享器的名稱與密碼,如下

const char* ssid = "(IP 分享器的名稱)";

const char* password = "(IP 分享器的密碼)";


Step4 點擊下拉功能表 [工具] > [開發板],選擇 ESP32 Wrover Module。


Step5 由於這個程式碼比較大,需要較多的空間,所以我們要指定這個選項,點擊下拉功能表 [工具] > [Partition Scheme: "Huge APP (3MB No OTA...]。


Step6 點擊下拉功能表 [工具] > [序列埠]。我的是 COM3,請您選擇自己的序列埠。


Step7 點擊上傳程式。

如果訊息欄出現  "Connecting ..... ___ ..... ___ .....",此時請您按一下(按下後放開) ESP32-Cam 開發板側邊上的按鈕。如果正常,它接著應該會出現上傳進度的百分比。

Step8 如果顯示"上傳完畢",請移除 ESO32-Cam 開發板上的 IO0 --- Gnd 接線。

Step9 開啟「序列埠監視視窗」,將鮑率調到 115200,然後再按一下(按後放開) ESP32-Cam 開發板上的按鈕。您可以發現序列埠出現一些訊息,其中 http://192.168.xx.xxx 就是 ESP32-Cam 的 網路位址。

Step10 將 ESP32-Cam 的網路位址複製並貼到瀏覽器,按下 <Enter> 鍵,您就可以看到如下畫面

Step11 用滑鼠點擊畫面下方的 [Start Stream] 按鈕,然後您就可以看到視窗裡出現攝影機的畫面了。

如果要關閉攝影機,您可以在同一個位置按一下 [Stop Stream] 按鈕。

您可以隨意更改網頁中的選項數據,試試各種功能和效果,這裡我們就不多說了。


後記

1. 如果在上傳程式時出現類似 "找不到序列埠訊息...",建議您更換一個好一點的 USB2TTL 模組。

2. 如果能正常上載程式,但板子無法正常工作時,請您檢查是否有移除 ESP32-Cam 開發板針腳上的 IO0 --- Gnd 接線。

3. 因為 ESP32-Cam 開發板需要較穩定的電壓和較多的電流,如果您發現板子出現不穩定的狀態,請您更換較粗的電源線。

4. 可以上傳程式不表示 ESP32-Cam 開發板沒有問題,有些板子會出現無法連接到 IP 分享器的狀況,此時序列埠會一直出現連線狀態,如下圖 

5. 玩家沒安裝好鏡頭或產品本身的問題,影像可能會出現顏色異常的現象(例如黃色很淡或顯示不出來),所以初次使用 ESP32-Cam 開發板時,請您拿出各種顏色的物品讓鏡頭拍攝,仔細檢查一下顏色是否異常。


相關連結

人體偵測 https://www.youtube.com/watch?v=LBoM_Uoq_nA&t=38s

將影像儲存到 SD 卡 https://www.youtube.com/watch?v=2xOo-zorpYI

將影像儲存到 SD 卡 https://www.youtube.com/watch?v=eot6COwCPF0&t=4s


採購資訊

ESP32-Cam 開發板 https://www.ruten.com.tw/item/show?22018501441929

ESP32-Cam 開發寶典(書) https://www.ruten.com.tw/item/show?22023657681327

ESP32-Cam 雙軸雲台(基本款) https://www.ruten.com.tw/item/show?22024688065979

ESP32-Cam 雙軸雲台(人體偵測款) https://www.ruten.com.tw/item/show?22024688076465

ESP32-Cam Wifi 視訊小車套件 https://www.ruten.com.tw/item/show?21947911491952