#include "esp_camera.h"
#include "FS.h"
#include "SD_MMC.h"
#include <SPI.h>
#include <TFT_eSPI.h>
#include <JPEGDecoder.h>
// เซ็ตจอ TFT
TFT_eSPI tft = TFT_eSPI();
// เซ็นเซอร์เหรียญและปุ่ม
#define COIN_SENSOR_PIN 14
#define BUTTON_PIN 15
bool takePhotoFlag = false;
unsigned long lastDebounceTime = 0;
const unsigned long debounceDelay = 200;
// พินกล้อง 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
// ตัวแปรเก็บรายชื่อไฟล์
#define MAX_IMAGES 30
String imageFiles[MAX_IMAGES];
int imageCount = 0;
int currentImage = 0;
// ฟังก์ชันประกาศกล้อง
void initCamera() {
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;
config.frame_size = FRAMESIZE_VGA; // 640x480
config.jpeg_quality = 12; // คุณภาพ 10-20 ปรับได้
config.fb_count = 2;
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
Serial
.printf("Camera init failed with error 0x%x\n", err
); while(true);
}
}
// อ่านชื่อไฟล์ .jpg ใน SD card
void scanImages() {
File root
= SD_MMC
.open
("/"); imageCount = 0;
while(true) {
File entry
= root
.openNextFile
(); if (!entry) break;
String fname = entry.name();
if (fname.endsWith(".jpg") || fname.endsWith(".JPG")) {
if (imageCount < MAX_IMAGES) {
imageFiles[imageCount++] = fname;
Serial.println("Found image: " + fname);
}
}
entry.close();
}
}
void drawJpegFile
(File jpegFile
, int x
, int y
) { if (JpegDec.decodeSdFile(jpegFile)) {
uint16_t *pImg = JpegDec.pImage;
uint16_t mcu_w = JpegDec.MCUWidth;
uint16_t mcu_h = JpegDec.MCUHeight;
uint16_t max_x = x + JpegDec.width;
uint16_t max_y = y + JpegDec.height;
for (uint16_t j=0; j < JpegDec.MCUSPerCol; j++) {
for (uint16_t i=0; i < JpegDec.MCUSPerRow; i++) {
for (uint16_t y_pos=0; y_pos < mcu_h; y_pos++) {
tft.pushColors(&pImg[(j * mcu_h + y_pos) * mcu_w * JpegDec.MCUSPerRow + i * mcu_w], mcu_w);
}
}
}
} else {
Serial.println("JPEG decode failed");
}
}
void setup() {
Serial.begin(115200);
pinMode(COIN_SENSOR_PIN, INPUT);
pinMode(BUTTON_PIN, INPUT_PULLUP);
tft.init();
tft.setRotation(1);
tft.fillScreen(TFT_BLACK);
if(!SD_MMC.begin()){
Serial.println("SD Card Mount Failed");
while(true);
}
initCamera();
scanImages();
Serial
.printf("Total images: %d\n", imageCount
);}
void loop() {
// อ่านเซ็นเซอร์เหรียญ (Active LOW สมมติ)
if(digitalRead(COIN_SENSOR_PIN) == LOW || digitalRead(BUTTON_PIN) == LOW) {
unsigned long now = millis();
if(now - lastDebounceTime > debounceDelay) {
takePhotoFlag = true;
lastDebounceTime = now;
}
}
if(takePhotoFlag) {
takePhotoFlag = false;
// ถ่ายภาพ
camera_fb_t * fb = esp_camera_fb_get();
if(!fb) {
Serial.println("Camera capture failed");
return;
}
// สร้างชื่อไฟล์จากเวลาปัจจุบัน
String path = "/img_" + String(millis()) + ".jpg";
File file = SD_MMC
.open
(path
.c_str
(), FILE_WRITE
); Serial.println("Failed to open file for writing");
} else {
file.write
(fb
->buf, fb
->len); Serial.println("Saved: " + path);
// เพิ่มภาพใหม่ในอาเรย์
if(imageCount < MAX_IMAGES) {
imageFiles[imageCount++] = path;
}
}
esp_camera_fb_return(fb);
}
// แสดงภาพวนลูป
if(imageCount > 0) {
File imgFile
= SD_MMC
.open
(imageFiles
[currentImage
].c_str
()); if(imgFile) {
tft.fillScreen(TFT_BLACK);
drawJpegFile(imgFile, 0, 0);
imgFile.close();
currentImage++;
if(currentImage >= imageCount) currentImage = 0;
}
delay(5000); // แสดงภาพละ 5 วินาที
} else {
tft.fillScreen(TFT_BLACK);
tft.setCursor(10, 30);
tft.setTextColor(TFT_WHITE);
tft.setTextSize(2);
tft.println("No Images");
delay(2000);
}
}