วันอังคารที่ 17 กันยายน พ.ศ. 2562

สร้างกล้องเซลฟี่ ด้วย TTGO T-Camera Plus ESP32



TTGO T-Camera Plus ESP32  คือบอร์ด ESP32 ที่มีกล้อง OV2640 ความละเอียด 2 ล้านพิเซล ในตัว พร้อมจอภาพ IPS Panel ST7789 ขนาด 1.3 นิ้ว ซึ่งแอปพลิเคชั่นกล้องต้องการหน่วยความจำเพิ่มเติมดังนั้นจึงมีเพิ่ม Micro SD Card เข้าที่ช่องเสียบการ์ด


### อุปกรณ์ที่ใช้ ###


1. TTGO T-Camera Plus ESP32-DOWDQ6 Display Camera Module

2. Micro USB Cable Wire 1m

3. Micro SD Card Class 10 16GB + Card Adapter




โดยการทำโปรเจคมีขั้นตอนดังนี้


1.ติดตั้ง Arduino core for ESP32

ลิงค์การติดตั้ง Arduino core for ESP32

https://robotsiam.blogspot.com/2017/09/arduino-core-for-esp32.html


2.ติดตั้ง ไลบรารี่ arduino-selfie-camera


2.1 ดาวน์โหลด ไลบรารี่ arduino-selfie-camera

https://github.com/moononournation/arduino-selfie-camera


2.2 คลิกที่ Clone or download -> Download ZIP





2.3 เปิด โปรแกรม Arduino IDE ไปที่ Skecth -> Include Library -> Add .ZIP Library...






2.4 ไปที่ ไลบรารี arduino-selfie-camera-master.zip ที่เรา ดาวน์โหลด มา -> Open




2.5 ตรวจสอบที่ Skecth -> Include Library  จะพบ ไลบรารี arduino-selfie-camera-master เพิ่มเข้ามาใน Arduino IDE ของเรา






3.
ฟอร์แมต Micro SD Card


ติดตั้งโปรแกรม SD Card Formatter 5.0 ใช้สำหรับ Format Disk สามารถดาวน์โหลดได้จากลิงก์



https://www.sdcard.org/downloads/formatter_4/eula_windows/


จากนั้นให้คลิกปุ่ม Accept โปรแกรม SD Formatter โปรแกรมจึงจะดาวน์โหลดสู่ คอมพิวเตอร์ของเรา




Micro SD Card , Class 10 ความจุ 16GB + Card Adapter




ให้เสียบ Micro SD Card เข้าไปใน Card Adapter



Card Adapter ที่มี Micro SD Card อยู่ด้านใน




แล้วจึงเสียบ Card Adapter ที่บรรจุ Micro SD Card ใส่ในช่องซ็อกเก็ตของคอมพิวเตอร์ PC (ดันจนกระทั่งมีเสียงคลิก)






แล้วทำการ Format , Micro SD Card โดยใช้โปรแกรม  SD Card Formatter


ถ้าเสียบ Card Adapter ที่บรรจุ Micro SD Card ได้ถูกต้อง ที่ Select card จะมี ไดรฟ์ ให้เลือก ในตัวอย่างจะเป็น ไดรฟ์  F:/



เลือก Quick format และที่ Volume label ในตัวอย่างตั้งชื่อเป็น Robot



คลิก Format




คลิก Yes





คลิก OK  ถึงขั้นตอนนี้การ Format SD Card ของเรานั้นเสร็จสมบูรณ์แล้ว ให้ปิดโปรแกรมลงไป




4.เสียบ Micro SD Card เข้าไปใน Slot Card  



 .




5.อัพโหลดโปรแกรม


5.1 เชื่อมต่อสาย USB ระหว่าง คอมพิวเตอร์ กับ ESP32




5.2 เปิดโปรแกรม Arduino (IDE) และ ก็อปปี้ โค้ดด้านล่างนี้ ไปวางไว้ในส่วนเขียนโปรแกรม




#include <esp_camera.h>
#include <SD.h>
#include <FS.h>
#include <rom/tjpgd.h>
#include "cam.h"
#include "ST7789.h"
#include "tjpgdec.h"

#define SDCARA_CS 0
#define SNAP_QUALITY 6 // 1-63, 1 is the best

ST7789 tft = ST7789(); // Invoke library, pins defined in User_Setup.h

char tmpStr[256];
char nextFilename[31];
uint16_t fileIdx = 0;
int i = 0;
static uint16_t *preview;
sensor_t *s;
camera_fb_t *fb = NULL;
JPGIODEV dev;
char *work = NULL; // Pointer to the working buffer (must be 4-byte aligned)

void setup()
{
  Serial.begin(115200);
  // Serial.setDebugOutput(true);

  tft.init();
  tft.invertDisplay(true);
  tft.setSwapBytes(true);
  tft.setRotation(0);

  tft.fillScreen(TFT_BLACK);
  tft.setTextDatum(TL_DATUM);
  tft.setTextSize(2);
  tft.setTextColor(TFT_WHITE, TFT_RED);
  tft.drawString(" ESP", 0, 0);
  tft.setTextColor(TFT_WHITE, TFT_ORANGE);
  tft.drawString("32 ", 48, 0);
  tft.setTextColor(TFT_WHITE, TFT_GREEN);
  tft.drawString(" Camera ", 82, 0);
  tft.setTextColor(TFT_WHITE, TFT_BLUE);
  tft.drawString(" Plus  ", 172, 0);
  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  tft.setTextSize(1);

  if (!SD.begin(SDCARA_CS))
  {
    tft.drawString("SD Init Fail!", 0, 208);
    Serial.println("SD Init Fail!");
  }
  else
  {
    snprintf(tmpStr, sizeof(tmpStr), "SD Card Type: %d Size: %lu MB", SD.cardType(), SD.cardSize() / 1024 / 1024);
    tft.drawString(tmpStr, 0, 208);
    Serial.println(tmpStr);

    init_folder();

    xTaskCreate(
        findNextFileIdxTask,       /* Task function. */
        "FindNextFileIdxTask", /* String with name of task. */
        10000,                 /* Stack size in bytes. */
        work,                  /* Parameter passed as input of the task */
        1,                     /* Priority of the task. */
        NULL);                 /* Task handle. */
  }

  esp_err_t err = cam_init();
  if (err != ESP_OK)
  {
    snprintf(tmpStr, sizeof(tmpStr), "Camera init failed with error 0x%x", err);
    tft.drawString(tmpStr, 0, 208);
    Serial.println(tmpStr);
  }

  //drop down frame size for higher initial frame rate
  s = esp_camera_sensor_get();
  s->set_brightness(s, 2);
  s->set_contrast(s, 2);
  s->set_saturation(s, 2);
  s->set_sharpness(s, 2);
  s->set_aec2(s, true);
  s->set_denoise(s, true);
  s->set_lenc(s, true);
  s->set_hmirror(s, true);
  //s->set_vflip(s, true);
  s->set_quality(s, 63);

  preview = new uint16_t[200 * 150];
  work = (char *)malloc(WORK_BUF_SIZE);
  dev.linbuf_idx = 0;
  dev.x = 0;
  dev.y = 0;
  dev.linbuf[0] = (color_t *)heap_caps_malloc(JPG_IMAGE_LINE_BUF_SIZE * 3, MALLOC_CAP_DMA);
  dev.linbuf[1] = (color_t *)heap_caps_malloc(JPG_IMAGE_LINE_BUF_SIZE * 3, MALLOC_CAP_DMA);
}

esp_err_t cam_init()
{
  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;
  // init with high specs to pre-allocate larger buffers
  config.frame_size = FRAMESIZE_UXGA;
  config.jpeg_quality = SNAP_QUALITY;
  config.fb_count = 2;

  // camera init
  return esp_camera_init(&config);
}

void init_folder()
{
  File file = SD.open("/DCIM");
  if (!file)
  {
    Serial.println("Create /DCIM");
    SD.mkdir("/DCIM");
  }
  else
  {
    Serial.println("Found /DCIM");
    file.close();
  }
  file = SD.open("/DCIM/100ESPDC");
  if (!file)
  {
    Serial.println("Create /DCIM/100ESPDC");
    SD.mkdir("/DCIM/100ESPDC");
  }
  else
  {
    Serial.println("Found /DCIM/100ESPDC");
    file.close();
  }
}

void findNextFileIdxTask(void *parameter)
{
  findNextFileIdx();
  vTaskDelete(NULL);
}

void findNextFileIdx()
{ // TODO: revise ugly code
  fileIdx++;
  File file;
  snprintf(nextFilename, sizeof(nextFilename), "/DCIM/100ESPDC/DSC%05D.JPG", fileIdx);
  file = SD.open(nextFilename);
  if (!file)
  {
    Serial.printf("Next file: %s\n", nextFilename);
    return;
  }
  else
  {
    for (int k = 1000; k <= 30000; k += 1000)
    {
      snprintf(nextFilename, sizeof(nextFilename), "/DCIM/100ESPDC/DSC%05D.JPG", fileIdx + k);
      file = SD.open(nextFilename);
      if (file)
      {
        Serial.printf("Found %s\n", nextFilename);
        file.close();
      }
      else
      {
        Serial.printf("Not found %s\n", nextFilename);
        k -= 1000;
        for (int h = 100; h <= 1000; h += 100)
        {
          snprintf(nextFilename, sizeof(nextFilename), "/DCIM/100ESPDC/DSC%05D.JPG", fileIdx + k + h);
          file = SD.open(nextFilename);
          if (file)
          {
            Serial.printf("Found %s\n", nextFilename);
            file.close();
          }
          else
          {
            Serial.printf("Not found %s\n", nextFilename);
            h -= 100;
            for (int t = 10; t <= 100; t += 10)
            {
              snprintf(nextFilename, sizeof(nextFilename), "/DCIM/100ESPDC/DSC%05D.JPG", fileIdx + k + h + t);
              file = SD.open(nextFilename);
              if (file)
              {
                Serial.printf("Found %s\n", nextFilename);
                file.close();
              }
              else
              {
                Serial.printf("Not found %s\n", nextFilename);
                t -= 10;
                for (int d = 1; d <= 10; d++)
                {
                  snprintf(nextFilename, sizeof(nextFilename), "/DCIM/100ESPDC/DSC%05D.JPG", fileIdx + k + h + t + d);
                  file = SD.open(nextFilename);
                  if (file)
                  {
                    Serial.printf("Found %s\n", nextFilename);
                    file.close();
                  }
                  else
                  {
                    Serial.printf("Next file: %s\n", nextFilename);
                    fileIdx += k + h + t + d;
                    return;
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

void snap()
{
  s->set_hmirror(s, false);
  //s->set_vflip(s, false);
  s->set_quality(s, SNAP_QUALITY);

  tft.fillRect(20, 45, 200, 150, TFT_DARKGREY);

  fb = esp_camera_fb_get();
  esp_camera_fb_return(fb);
  fb = NULL;

  tft.fillRect(20, 45, 200, 150, TFT_LIGHTGREY);

  fb = esp_camera_fb_get();
  if (!fb)
  {
    tft.drawString("Camera capture JPG failed", 0, 208);
    Serial.println("Camera capture JPG failed");
  }
  else
  {
    File file = SD.open(nextFilename, FILE_WRITE);
    if (file.write(fb->buf, fb->len))
    {
      tft.fillRect(20, 45, 200, 150, TFT_LIGHTGREY);
      snprintf(tmpStr, sizeof(tmpStr), "File written: %luKB\n%s", fb->len / 1024, nextFilename);
      tft.drawString(tmpStr, 0, 224);
      Serial.println(tmpStr);
    }
    else
    {
      tft.drawString("Write failed!", 0, 224);
      Serial.println("Write failed!");
    }
    esp_camera_fb_return(fb);
    fb = NULL;
    file.close();
  }

  s->set_hmirror(s, true);
  //s->set_vflip(s, true);
  s->set_quality(s, 63);
  fb = esp_camera_fb_get();
  esp_camera_fb_return(fb);
  fb = NULL;
}

void enterSleep()
{
  tft.end();
  esp_deep_sleep_start();
}

void loop()
{
  if (i == 1) // count down
  {
    tft.setTextSize(2);
    tft.drawString("3", 116, 24);
    Serial.println("3");
  }
  else if (i == 5)
  {
    tft.setTextSize(2);
    tft.drawString("2", 116, 24);
    Serial.println("2");
  }
  else if (i == 9)
  {
    tft.setTextSize(2);
    tft.drawString("1", 116, 24);
    Serial.println("1");
  }
  else if (i == 13) // start snap 
  {
    tft.setTextSize(2);
    tft.drawString("Cheeze!", 92, 24);
    Serial.println("Cheeze!");

    snap();
  }
  else if (i == 15)
  {
    tft.setTextSize(2);
    tft.drawString("Cheeze!!", 92, 24);
    Serial.println("Cheeze!!");

    findNextFileIdx();
    snap();
  }
  else if (i == 17)
  {
    tft.setTextSize(2);
    tft.drawString("Cheeze!!!", 92, 24);
    Serial.println("Cheeze!!!");

    findNextFileIdx();
    snap();

    tft.setTextSize(2);
    tft.drawString("Reset to snap again!", 0, 24);
    Serial.println("Reset to snap again!");

    decodeJpegFile(nextFilename, 3);
    tft.pushRect(20, 45, 200, 150, preview);
    delay(5000);
    Serial.println("Enter deep sleep...");
    enterSleep();
  }
  else
  {
    fb = esp_camera_fb_get();
    if (!fb)
    {
      Serial.printf("Camera capture failed!");
      tft.drawString("Camera capture failed!", 0, 224);
    }
    else
    {
      decodeJpegBuff(fb->buf, fb->len, 3);
      tft.pushRect(20, 45, 200, 150, preview);
      esp_camera_fb_return(fb);
      fb = NULL;
    }
  }

  i++;
}

// User defined call-back function to input JPEG data from file
//---------------------
static UINT tjd_file_input (
 JDEC* jd,  // Decompression object
 BYTE* buff,  // Pointer to the read buffer (NULL:skip)
 UINT nd   // Number of bytes to read/skip from input stream
)
{
 int rb = 0;
 // Device identifier for the session (5th argument of jd_prepare function)
 JPGIODEV *dev = (JPGIODEV*)jd->device;

 if (buff) { // Read nd bytes from the input strem
  rb = dev->f.read(buff, nd);
  return rb; // Returns actual number of bytes read
 }
 else { // Remove nd bytes from the input stream
  if (dev->f.seek(dev->f.position() + nd)) return nd;
  else return 0;
 }
}

// User defined call-back function to input JPEG data from memory buffer
//-------------------------
static UINT tjd_buf_input(
    JDEC *jd,   // Decompression object
    BYTE *buff, // Pointer to the read buffer (NULL:skip)
    UINT nd     // Number of bytes to read/skip from input stream
)
{
  // Device identifier for the session (5th argument of jd_prepare function)
  JPGIODEV *dev = (JPGIODEV *)jd->device;
  if (!dev->membuff)
    return 0;
  if (dev->bufptr >= (dev->bufsize + 2))
    return 0; // end of stream

  if ((dev->bufptr + nd) > (dev->bufsize + 2))
    nd = (dev->bufsize + 2) - dev->bufptr;

  if (buff)
  { // Read nd bytes from the input strem
    memcpy(buff, dev->membuff + dev->bufptr, nd);
    dev->bufptr += nd;
    return nd; // Returns number of bytes read
  }
  else
  { // Remove nd bytes from the input stream
    dev->bufptr += nd;
    return nd;
  }
}

// User defined call-back function to output RGB bitmap to display device
//----------------------
static UINT tjd_output(
    JDEC *jd,     // Decompression object of current session
    void *bitmap, // Bitmap data to be output
    JRECT *rect   // Rectangular region to output
)
{
  BYTE *src = (BYTE *)bitmap;
  // Serial.printf("%d, %d, %d, %d\n", rect->top, rect->left, rect->bottom, rect->right);
  for (int y = rect->top; y <= rect->bottom; y++)
  {
    for (int x = rect->left; x <= rect->right; x++)
    {
      preview[y * 200 + x] = tft.color565(*(src++), *(src++), *(src++));
    }
  }
  return 1; // Continue to decompression
}

void decodeJpegBuff(uint8_t arrayname[], uint32_t array_size, uint8_t scale)
{
  JDEC jd; // Decompression object (70 bytes)
  JRESULT rc;

  //dev.fhndl = NULL;
  // image from buffer
  dev.membuff = arrayname;
  dev.bufsize = array_size;
  dev.bufptr = 0;

  if (scale > 3)
    scale = 3;

  if (work)
  {
    rc = jd_prepare(&jd, tjd_buf_input, (void *)work, WORK_BUF_SIZE, &dev);
    if (rc == JDR_OK)
    {
      // Start to decode the JPEG file
      rc = jd_decomp(&jd, tjd_output, scale);
    }
  }
}

void decodeJpegFile(char filename[], uint8_t scale)
{
  JDEC jd; // Decompression object (70 bytes)
  JRESULT rc;

  dev.f = SD.open(filename);
  // image from buffer
  //dev.membuff = null;
  dev.bufsize = JPG_IMAGE_LINE_BUF_SIZE;
  dev.bufptr = 0;

  if (scale > 3)
    scale = 3;

  if (work)
  {
    rc = jd_prepare(&jd, tjd_file_input, (void *)work, WORK_BUF_SIZE, &dev);
    if (rc == JDR_OK)
    {
      // Start to decode the JPEG file
      rc = jd_decomp(&jd, tjd_output, scale);
    }
  }
}



ดาวน์โหลดโค้ด :


https://drive.google.com/open?id=1qsyOCNZRPyIpr72bs7f1DzmVua4qYKX9


5.3 ไปที่ Tools -> Board เลือก ESP32 Dev Module




5.4 ไปที่ Tools -> PSRAM: เลือกเป็น Enabled






5.5 ไปที่ Tools -> Port แล้วเลือกพอร์ตที่ปรากฏ (กรณีใช้เครื่องคอมพิวเตอร์ที่มี COM Port ให้เลือกตัวอื่นที่ไม่ใช่ COM1)

ในตัวอย่างเลือกเป็น "COM20"





5.6 กดปุ่ม   เพื่ออัพโหลด


ตั้งชื่อไฟล์ -> Save โปรแกรม จะทำการ อัพโหลด







5.7 หากสามารถอัพโหลดโปรแกรมลงบอร์ดได้สำเร็จ จะแสดงคำว่า Done uploading. ที่แถบด้านล่าง




6. ทดสอบการทํางาน







1. กดปุ่มรีเซ็ตเพื่อเปิดกล้อง T-Camera Plus ESP32
2. กล้องนับถอยหลังจาก 3
3. ปรับมุมที่คุณชื่นชอบ
4. กล้องถ่ายรูปเริ่มถ่ายภาพ 3 ภาพติดต่อกัน
5. เล่นภาพที่ถ่ายครั้งสุดท้ายโดยอัตโนมัติ
6. เข้าสู่ Sleep Mode หลังจาก 5 วินาที
7. กดปุ่มรีเซ็ตเพื่อถ่ายอีกครั้ง




7. วีดีโอ ผลลัพล์การทำงาน








credit :  https://www.instructables.com/id/Arduino-Selfie-Camera/

ไม่มีความคิดเห็น:

แสดงความคิดเห็น