วันจันทร์ที่ 23 กันยายน พ.ศ. 2562

ทดสอบการเคลื่อนที่ของหุ่นยนต์ DevKitC ESP32 V4



GPIO
ย่อมาจาก general purpose input/output เรียกเป็นภาษาไทยง่ายๆว่า พอร์ตเอนกประสงค์ คือเราสามารถควบคุม คอนโทรลให้เป็น ค่าต่างๆได้ และเรายังสามารถกำหนด GPIO เหล่านี้ให้เป็น INPUT หรือ OUTPUT ก็ได้

DevKitC ESP32  มี GPIO ทั้งหมด 32 ขา  ในการใช้งาน GPIO มีสิ่งที่ต้องพึงระมัดระวังให้มากเป็นพิเศษก็คือเรื่อง ในการต่อสาย เนื่องจาก GPIO เหล่านี้ ใช้ไฟ 3.3 โวลต์ และทนแรงดันไฟฟ้าได้แค่ 3.3V เท่านั้น ดังนั้นถ้าหากใช้ไฟ 5 โวลต์ มาต่อเข้ากับ port นี้ก็จะพังทันที ไม่สามารถใช้งานได้อีกต่อไป และไม่สามารถเปลี่ยน หรือ ซ่อมแซมได้


การควบคุมโหมดใช้งานขา GPIO แบบดิจิตอล

ในการใช้งานขา GPIO แบบดิจิตอล จำเป็นต้องมีการควบคุมหรือบอกให้ขา GPIO นั้น ๆ ได้รู้ว่าต้องการจะให้ใช้รับค่าดิจิตอลเข้ามา หรือจะให้เขียนค่าออกไป ซึ่งจะใช้คำสั่ง pinMode() ในการกำหนด

คำสั่ง pinMode() มีรูปแบบการใช้งานดังนี้

      void pinMode( pin, mode);

จะเห็นได้ว่าฟังก์ชั่นมีค่าพารามิเตอร์ 2 ตัว คือ

pin – กำหนดหมายเลขขา GPIO ที่ต้องการควบคุม
mode – โหมดที่ต้องการกำหนดให้ขา GPIO ซึ่งสามารถเป็นได้ดังนี้
INPUT – กำหนดให้ขา GPIO มีสถานะเป็นอินพุต รอรับค่าเข้ามา
OUTPUT – กำหนดให้ขา GPIO มีสถานะเป็นเอาต์พุต รอเขียนค่าออกไป



การกำหนดขา GPIO ของ DevKitC ESP32



โดยดูได้จาก PINOUT  ของ DevKitC ESP32 ตามภาพด้านล่าง




โดยให้ใช้ตัวเลขหลัง GPIO ในการกำหนดขาใช้งาน ตัวอย่างเช่น


pinMode(12, OUTPUT);


คือ กำหนดให้ GPIO ขาที่ 12 ให้มีสถานะเป็นเอาต์พุต รอเขียนค่าออกไป

ส่วนใหญ่นิยมเขียนที่ ฟังก์ชั่น void setup()


การเขียนค่าเอาต์พุตแบบดิจิตอล



เอาต์พุตแบบดิจิตอล คือการใช้งานขา GPIO ในการ เขียนค่าสถานะแบบดิจิตอล ซึ่งใช้งานตั้งอยู่บนพื้นฐานดิจิตอล คือค่าที่ได้หรือค่าที่เขียนจะมีแค่ 2 สถานะ คือสถานะ HIGH และสถานะ LOW  ซึ่งขา GPIO จะรองรับการจ่ายกระแสสูงสุดเพียง 12mA เท่านั้น

ซึ่งการเขียนค่าเอาต์พุตแบบดิจิตอลจะใช้คำสั่ง digitalWrite() มีรูปแบบการใช้งานดังนี้

 digitalWrite(pin, value);

มีค่าพารามิเตอร์ 2 ตัว คือ

pin – กำหนดหมายเลขขา GPIO ที่ต้องการเขียนค่า
value – ค่าที่ต้องการเขียนออกไป สามารถเป็นได้ดังนี้
HIGH – เขียนค่าลอจิก 1 หรือสถานะ HIGH หรือแรงดัน 3V ออกไป
LOW – เขียนค่าลอจิก 0 หรือสถานะ LOW หรือเทียบเท่ากับขากราว์ด

ตัวอย่างเช่น

digitalWrite(12, HIGH);

คือ กำหนดให้ GPIO ขาที่ 12 ให้มีสถานะ HIGH หรือแรงดัน 3V ออกไป

ส่วนใหญ่นิยมเขียนที่ ฟังก์ชั่น void loop()


เตรียมความพร้อม ประกอบหุ่นยนต์ DevKitC ESP32


ลิงค์การประกอบหุ่นยนต์ DevKitC ESP32


https://esp32robot.blogspot.com/2019/09/devkitc-esp32-v4-car.html


ทดสอบการเคลื่อนที่ของหุ่นยนต์ และ เอาต์พุตแบบดิจิตอล



เราจะใช้ หุ่นยนต์ DevKitC ESP32 ที่เราประกอบเสร็จแล้วในขั้นตอนที่ผ่านมา ทดสอบการเคลื่อนที่ของหุ่นยนต์ และ การใช้งานเอาต์พุตแบบดิจิตอล


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


1 . 4WD Smart Robot Car Chassis Kits

2. DevKitC V4 ESP32 Development Board ESP-WROOM-32D

3. Micro USB Cable Wire 1m

4. Breadboard 8.5CM x 5.5CM 400 holes  //  จำนวน 2 ชิ้น

5. Motor Driver Module L298N

6. สกรูหัวกลม+น็อตตัวเมีย ขนาด 3มม ยาว 12มม

7. Jumper 20cm Male to Male

8. Jumper 20cm Female to Male

9. เพาเวอร์สวิตซ์สำหรับเปิดปิด

10. รางถ่าน 18650 แบบ 2 ก้อน

11. ถ่านชาร์จ 18650 Panasonic NCR18650B 3.7v 3400mAh  // จำนวน 2 ก้อน

...

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



เปิด โปรแกรม Arduino IDE ขึ้นมา เขียนโปรแกรม หรือ  Sketch  ตามโค้ดด้านล่างนี้



// Motor A pins

int pinA2 = 12;
int pinA1 = 13;


//Motor B pins

int pinB2 = 32;
int pinB1 = 33;


boolean run = true;

void setup() {

  pinMode(pinA1, OUTPUT);
  pinMode(pinA2, OUTPUT);


  pinMode(pinB1, OUTPUT);
  pinMode(pinB2, OUTPUT);

}

void loop() {
  if (run) {
    delay(2000);
    //Go forward
    forward(1000);
    //Go backward
    backward(1000);
    //Turn left
    turnLeft(1000);
    coast(200);
    //Turn right
    turnRight(1000);
    coast(200);
    //This stops the loop
    run = false;
  }
}


//ฟังก์ชั่นหลักในการควบคุมมอเตอร์


void forward(int time)
{
  motorAForward();
  motorBForward();
  delay(time);
}

void backward(int time)
{
  motorABackward();
  motorBBackward();
  delay(time);
}

void turnLeft(int time)
{
  digitalWrite(pinA1, LOW);
  digitalWrite(pinA2, LOW);
  digitalWrite(pinB1, HIGH);
  digitalWrite(pinB2, LOW);
  delay(time);
}

void turnRight(int time)
{
  digitalWrite(pinA1, HIGH);
  digitalWrite(pinA2, LOW);
  digitalWrite(pinB1, LOW);
  digitalWrite(pinB2, LOW);
  delay(time);
}

void coast(int time)
{
  motorACoast();
  motorBCoast();
  delay(time);
}

void brake(int time)
{
  motorABrake();
  motorBBrake();
  delay(time);
}


//ฟังก์ชั่นรองในการควบคุมมอเตอร์


//motor A controls
void motorAForward()
{
  digitalWrite(pinA1, HIGH);
  digitalWrite(pinA2, LOW);
}

void motorABackward()
{
  digitalWrite(pinA1, LOW);
  digitalWrite(pinA2, HIGH);
}

//motor B controls
void motorBForward()
{
  digitalWrite(pinB1, HIGH);
  digitalWrite(pinB2, LOW);
}

void motorBBackward()
{
  digitalWrite(pinB1, LOW);
  digitalWrite(pinB2, HIGH);
}

//coasting and braking
void motorACoast()
{
  digitalWrite(pinA1, LOW);
  digitalWrite(pinA2, LOW);
}

void motorABrake()
{
  digitalWrite(pinA1, HIGH);
  digitalWrite(pinA2, HIGH);
}

void motorBCoast()
{
  digitalWrite(pinB1, LOW);
  digitalWrite(pinB2, LOW);
}

void motorBBrake()
{
  digitalWrite(pinB1, HIGH);
  digitalWrite(pinB2, HIGH);
}




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



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

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


ไปที่ Tools -> Upload Speed : เลือกเป็น "115200"


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

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



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


ใสถ่าน แบบ 18650 แรงดันไฟเฉลี่ย 3.7V  (3400 mAh)  จำนวน 2 ก้อน



แล้วทดสอบการทำงาน โปรแกรมนี้จะทำงานเพียง 1 ครั้ง ถ้าต้องการทดลองใหม่ให้  ปิดเปิด สวิทช์ไฟใหม่

ยกลงวางที่พื้นแล้วทดสอบ เปิด สวิทช์ไฟ ถ้าทุกอย่างถูกต้อง รถจะเคลื่อนที่ไปข้างหน้า-ถอยหลัง แล้ว เลี้ยวซ้าย แล้ว เลี้ยวขวา กลับสู่ตำแหน่งเดิม


วีดีโอผลลัพธ์ ทดสอบการเคลื่อนที่ของหุ่นยนต์ DevKitC ESP32


...

ประกอบหุ่นยนต์ DevKitC ESP32 V4


การจะทดสอบผลลัพธ์ การเขียนโปรแกรมคอมพิวเตอร์ และ การทำงานของ ไมโครคอนโทรลเลอร์ หุ่นยนต์ DIY (DIY  ย่อมาจาก Do it yourself ซึ่งมีความหมายว่า ทำสิ่งต่างๆด้วยตัวเอง) ก็เป็นทางเลือกหนึ่ง ซึ่งทั้งให้ความรู้ และ ความสนุก ให้กับผู้ทดสอบ และ ยังใช้ประยุกต์ไปใช้ กับงานด้านอื่นๆ รวมทั้งเป็นพื้นฐานในการ  พัฒนาหุ่นยนต์ ในระดับ ต่อๆไปได้อีกด้วย

ดังนั้นเราจึงเลือก สร้างหุ่นยนต์ DIY สำหรับ ไว้ใช้ ทดสอบผลลัพธ์ การเรียนรู้ การเขียนโปรแกรมคอมพิวเตอร์ และ การทำงานของ ไมโครคอนโทรลเลอร์ DevKitC ESP32 ของเรา


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


1 . 4WD Smart Robot Car Chassis Kits

2. DevKitC V4 ESP32 Development Board ESP-WROOM-32D

3. Micro USB Cable Wire 1m

4. Breadboard 8.5CM x 5.5CM 400 holes  //  จำนวน 2 ชิ้น

5. Motor Driver Module L298N

6. สกรูหัวกลม+น็อตตัวเมีย ขนาด 3มม ยาว 12มม

7. Jumper 20cm Male to Male

8. Jumper 20cm Female to Male

9. เพาเวอร์สวิตซ์สำหรับเปิดปิด

10. รางถ่าน 18650 แบบ 2 ก้อน


### ภาพรวมการต่อ มอเตอร์-L298N-รางถ่าน ###







เริ่มต้น ด้วย ใช้  Jumper 20cm Male to Male ผู้-ผู้ บัดกรี เข้ากับ มอเตอร์ ทั้ง 4 ตัว โดยเลือกสีของ Jumper  ให้เหมือนกัน เป็นคู่ๆ เพื่อกำหนดขั้วของมอเตอร์ และ การจับคู่มอเตอร์

*** ในตัวอย่าง  ****

มอเตอร์ 2 ตัวบน ใช้ Jumper สีขาว อยู่ด้านล่าง และ สีดำ อยู่ด้านบน
มอเตอร์ 2 ตัวล่าง ใช้ Jumper สีม่วง อยู่ด้านล่าง และ สีเทา อยู่ด้านบน



ประกอบมอเตอร์ 2 ตัว ด้านบน เข้ากับโครงของหุ่นยนต์ชั้นที่ 1

โดย... เชื่อมต่อสาย สีดำ จาก มอเตอร์ตัวที่ 1 ไปยัง มอเตอร์ตัวที่ 2 ที่เป็นสายสีดำเหมือนกัน และ เชื่อมต่อสาย สีขาว จาก มอเตอร์ตัวที่ 1 ไปยัง มอเตอร์ตัวที่ 2 ที่เป็นสายสีขาวเหมือนกัน




ประกอบมอเตอร์ 2 ตัว ด้านล่าง เข้ากับโครงของหุ่นยนต์

โดย... เชื่อมต่อสาย สีเทา จาก มอเตอร์ตัวที่ 3 ไปยัง มอเตอร์ตัวที่ 4 ที่เป็นสายสีเทาเหมือนกัน และ เชื่อมต่อสาย สีม่วง จาก มอเตอร์ตัวที่ 3 ไปยัง มอเตอร์ตัวที่ 4 ที่เป็นสายสีม่วงเหมือนกัน



เมื่อประกอบเสร็จ จะเหลือสาย 2 คู่ สำหรับ ต่อเข้ากับ Motor Driver ดังรูป



ยึดเสา 6 เสา สำหรับ วาง โครงหุ่นยนต์ชั้นที่ 2




ใช้ สกรูหัวกลม+น็อตตัวเมีย 12 มม.  ยึด รางถ่าน และ Motor Driver  เข้า กับ โครงหุ่นยนต์ชั้นที่ 2 ดังรูป




เพื่อให้การใช้งาน DevKitC ESP32 ให้เต็มประสิทธิภาพ ได้ครบทั้ง 38 ขา จึงแนะนำให้ ประกอบ Breadboard 2 ชิ้น เข้าด้วยกัน

โดย แยกชิ้นส่วนของ Breadboard ด้านล่าง นำส่วน ไฟ + - ออกไป ดังรูป



ประกอบ Breadboard 2 ชิ้น เข้าด้วยกัน ดังรูป




แล้วจึง ติดลงบน ของโครงหุ่นยนต์ชั้นที่ 2




เสียบ DevKitC ESP32 ลงไบที่ Breadboard ดังรูป




ประกอบ โครงหุ่นยนต์ชั้นที่ 2 เข้ากับ โครงหุ่นยนต์ชั้นที่ 1




เชื่อมต่อสายจากมอเตอร์ ทั้ง 2 คู่ เข้า กับ Motor Driver โดยเชื่อมต่อ สีของสายไฟ และ ด้านซ้าย- ด้านขวา ดังรูป




เชื่อมต่อ สายสีดำจากรางถ่าน ไปยัง ไฟ-  (แถบสีฟ้า) ของ Breadboard




สวิตซ์สำหรับเปิดปิด จะมี 2 ขา ให้เชื่อมต่อสายสีแดงจากรางถ่านเข้ากับ ขาของ สวิตซ์สำหรับเปิดปิด ด้านนึง และ เชื่อมต่อ จากขาของ  สวิตซ์สำหรับเปิดปิด ด้านที่เหลือ ไปยัง ไปยัง ไฟ+  (แถบสีแดง) ของ Breadboard




ใช้สายสีแดง เชื่อมต่อสาย จาก ไฟ+  (แถบสีแดง) ของ Breadboard เข้ากับ ไฟ+ ของ Motor Driver และ ใช้สายสีน้ำตาล เชื่อมต่อ จาก ไฟ-  (แถบสีฟ้า) ของ Breadboard เข้ากับ GND ของ  Motor Driver ดังรูป




### ภาพรวมการต่อ ESP32-L298N ###





เชื่อมต่อ สายสีน้ำเงิน เข้าที่ IN1 , สายสีเขียวเข้าที่ IN2 , สายสีเหลือง เข้าที่ IN3 , สายสีส้ม เข้าที่ IN4 ของ Motor Driver ดังรูป



เชื่อมต่อไปยัง DevKitC ESP32 โดย

สายสีน้ำเงิน ไปยังขา 13
สายสีเขียว ไปยังขา 12
สายสีเหลือง ไปยังขา 33
สายสีส้ม ไปยังขา 32



ภาพการเชื่อมต่อ ระหว่าง Motor Driver กับ DevKitC ESP32




### ภาพรวมการต่อ ESP32-L298N-รางถ่าน ###





ใช้สายสีแดง เชื่อมต่อสาย จาก ไฟ+  (แถบสีแดง) ของ Breadboard เข้ากับ 5V และ ใช้สายสีน้ำตาล เชื่อมต่อ จาก ไฟ-  (แถบสีฟ้า) ของ Breadboard เข้ากับ GND ของ  DevKitC ESP32




โดย สายสีแดงเข้าที่ขา 5V และ สายสีน้ำตาลเข้าที่ขา GND ดังรูป





ภาพรวม การประกอบหุ่นยนต์ DevKitC ESP32 ด้านซ้าย



ภาพรวม การประกอบหุ่นยนต์ DevKitC ESP32 ด้านขวา


ภาพรวม การประกอบหุ่นยนต์ DevKitC ESP32 ด้านหน้า


ถึงขั้นตอนนี้ หุ่นยนต์ DevKitC ESP32 ของเรานั้นก็พร้อมจะทำงานในขั้นตอนต่อไป

วันอังคารที่ 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/