2020年12月10日 星期四

[Mecanum] 麥克納姆輪金屬底盤小車組裝說明

這個麥克納姆輪小車底盤長25.5cm 寬15cm。底盤採用鋁合金材質,堅固耐用。

麥克納姆輪採用注塑工藝製造,並非 3D 列印,質量可靠。麥輪直徑約 60mm,適用於市售制式小車底盤。


準備材料

小車底盤套件 1件
馬達電源線(長約20CM)  8條



組裝步驟

Step1 先把 4 只馬達都焊上電源線。
提醒您:最好電源線的正負(紅黑線)位置都和相片中的一樣。



Step2 拿出鋁質底盤和螺絲,將馬達鎖上。
提醒您:
1. 鋁質底盤的塑膠護膜,只要不妨礙輪子轉動即可不必撕除。
2. 馬達有焊電線那一側靠車體內部。
3. 螺絲請務必鎖緊。



Step3 其它 3 個馬達也如 Step2 方式鎖上。



Step4 由於輪框並非確實固定在輪子上,小車行走時有可能會脫落,因此建議將輪框塗抹膠水黏上輪子,或是用 M2.3*12~20mm 螺絲從外側穿過輪框、輪子、聯軸器鎖緊到TT馬達


Step5 至此大功告成。
提醒您:請注意 4 個輪子胎紋的位置,必須確實正確。


這是正面



Step6 如果有 Arduino Uno 開發板,可以使用 M3*12 螺絲、螺帽固定。18650 雙節鋰電池盒,則可以使用 M3*6 螺絲、螺帽固定,如下圖。

提醒您:
1. 因金屬板具有導電性,因此最好在Arduino Uno 開發板和金屬底板之間放置墊片。
2. 圖片中標註"a"字樣者為 Arduino Uno 開發板固定處。




Step7 將 L293D 馬達驅動板插到 Uno 開發板上面。



Step8 至於手機藍牙遙控這部分,請參考「手機藍芽遙控麥克納姆輪車」。



相關鏈結

麥克納姆輪 PID 演算法 購買「小車底盤+藍牙電控」全套者,可賣場留言索取。

2020年12月6日 星期日

[ESP32-Cam] ESP32-Cam 單軸雲台組裝

 ESP32-Cam 雖然有攝影鏡頭,但若是固定式的,它的視野將只侷限在一個限定的區域。透過這組「ESP32-Cam單軸雲台」套件,可以讓ESP32-Cam的鏡頭左右各擺動90度,讓您的視野更加廣闊。

本套件可以很方便安裝在各式市售的小車底盤上。


準備材料


或是可以到下列賣場購買

奇摩 https://tw.bid.yahoo.com/item/100984760432

露天 https://www.ruten.com.tw/item/show?22049213363904

蝦皮 https://shopee.tw/-RWG-ESP32-Cam-%E5%96%AE%E8%BB%B8%E9%9B%B2%E5%8F%B0-%E6%94%AF%E6%9E%B6-%E9%81%A9%E5%90%88%E5%90%84%E7%A8%AE%E5%B0%8F%E8%BB%8A%E5%BA%95%E7%9B%A4-i.14363185.4767927023


組裝步驟

Step1 用 M2 螺絲和螺帽將伺服馬達固定到支架。建議讓轉軸置中。



Step2 用伺服馬達所附的自攻螺絲,將塑膠搖臂固定到壓克力板上。需注意搖臂與螺絲的方向。


Step3 用 M2 螺絲和螺帽將 L 型塑膠片鎖到大壓克力板上。並用束帶將 ESP32-Cam 開發板固定到大壓克力板上。



這是背面

Step4 組合大壓克力板部件和搖臂部件,用 M2 螺絲和螺帽將 L 型塑膠片鎖緊到搖臂部件上。


Step5 用 M3 螺絲和螺帽將伺服馬達部件鎖到小車底盤上。


Step6 將 Step4 部件放置到伺服馬達上,並用馬達所附之 M2.5 螺絲固定。


Step7 試寫程式上傳到 ESP32-Cam,並測試鏡頭左右擺動90度是否正常。至此完成。



2020年10月27日 星期二

[Arduino] 特雷門(Theremin) 電波琴

特雷門(Theremin) 電波琴是甚麼玩意兒? 如果您還不知道,可以參考一下維基百科 。


所需材料

1. Arduino Nano(Uno也可以) 開發板 *1

2. 麵包板 *1

3. PAM8403 功放模組 *1

4. 光敏電阻(LDR) *1

5. 10K 電阻 *1

6. 小喇叭(蜂鳴器也可以) *1

7. 公母頭杜邦線 *若干

8. 公排針 *若干

如果您沒有以上材料,也可以到露天賣場購買。



焊接

1. 將功放模組焊上公排針。

2. 將公母頭杜邦線的公頭端焊上喇叭。

電路接線

1. 將 Arduino Nano 開發板插上麵包板。

2. 依照上面電路圖用杜邦線連接各單元。

3. 喇叭接功放模組單邊輸出即可。


程式

1. 下載函式庫 https://sensorium.github.io/Mozzi/

2. 將下列程式上傳到 Arduino。

//#include <ADC.h>

#include <MozziGuts.h>

#include <Oscil.h> // oscillator template

#include <tables/sin2048_int8.h> // sine table for oscillator

#include <RollingAverage.h>

#include <ControlDelay.h>


#define INPUT_PIN 0 // analog control input


unsigned int echo_cells_1 = 32;

unsigned int echo_cells_2 = 60;

unsigned int echo_cells_3 = 127;


#define CONTROL_RATE 64

ControlDelay <128, int> kDelay; // 2seconds


// oscils to compare bumpy to averaged control input

Oscil <SIN2048_NUM_CELLS, AUDIO_RATE> aSin0(SIN2048_DATA);

Oscil <SIN2048_NUM_CELLS, AUDIO_RATE> aSin1(SIN2048_DATA);

Oscil <SIN2048_NUM_CELLS, AUDIO_RATE> aSin2(SIN2048_DATA);

Oscil <SIN2048_NUM_CELLS, AUDIO_RATE> aSin3(SIN2048_DATA);


// use: RollingAverage <number_type, how_many_to_average> myThing

RollingAverage <int, 32> kAverage; // how_many_to_average has to be power of 2

int averaged;


void setup(){

  kDelay.set(echo_cells_1);

  startMozzi();

}


void updateControl(){

  int bumpy_input = mozziAnalogRead(INPUT_PIN);

  averaged = kAverage.next(bumpy_input);

  aSin0.setFreq(averaged);

  aSin1.setFreq(kDelay.next(averaged));

  aSin2.setFreq(kDelay.read(echo_cells_2));

  aSin3.setFreq(kDelay.read(echo_cells_3));

}


int updateAudio(){

  return 3*((int)aSin0.next()+aSin1.next()+(aSin2.next()>>1)

    +(aSin3.next()>>2)) >>3;

}


void loop(){

  audioHook();

}


上傳程式後,試著用手由遠到近遮住光敏電阻,光敏電阻受光程度會影響聲音的頻率。


觀賞影片


如果 Arduino 連接 Midi,將可以產生更有趣的聲音效果,如下影片





做完電波琴後,您也可以參考這篇文「用蜂鳴器播放超級瑪莉(Mario)音樂」,讓您的喇叭發出電玩般的聲音。

參考資料

https://www.hackster.io/Oniichan_is_ded/arduino-theremin-96fc6d?fbclid=IwAR0IxHx7tyu2cwGUJTBs6tmBP234j-XV-2stRpmPg76N2sv9NQQpdhUsFKk

https://www.instructables.com/Make-Your-Own-Simple-Theremin/
https://www.youtube.com/watch?v=57S3dylfw3I
https://www.youtube.com/watch?v=DnD92Q_Kpac
https://www.youtube.com/watch?v=IEoOSjzHYhE
https://www.youtube.com/watch?v=Xsjwt9nXue0


採購資訊

特雷門(Theremin) 電波琴套件 https://www.ruten.com.tw/item/show?22040528261934

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