This tutorial is part of: Makerfabs MaTouch AI ESP32S3 2.8" Camera
The latest MaTouch AI board integrate I2S voice input/I2S speaker/ 3 million camera OV3660/ 320*240 resolution display, with ESP32S3 strong processor& Wifi ability, to make this board a good tool/platform for AI development with ESP32.
Makerfabs MaTouch ESP32-S3 2.8 นิ้ว กล้องและหน้าจอสัมผัส: ภาพถ่าย 3MP ไปยังการ์ด SD
วิวไฟน์เดอร์สด ถ่ายภาพ 3 เมกะพิกเซล บันทึกลง SD พร้อมการประทับเวลาจริง
แอปกล้องถ่ายรูปที่ใช้งานได้จริง: วิวไฟน์เดอร์สดที่ประมาณ 25 เฟรมต่อวินาที ปุ่มสัมผัสสามปุ่ม และภาพถ่ายที่บันทึกลงการ์ด microSD พร้อมการประทับวันที่และเวลาที่ถูกต้องจากนาฬิกาที่ใช้แบตเตอรี่สำรองของบอร์ด
กล้องและหน้าจอสัมผัสทำงานบนบอร์ด MaTouch AI ESP32-S3
ปุ่มทั้งสาม
SNAP - ถ่ายภาพทันทีที่ความละเอียดวิวไฟน์เดอร์ รวดเร็วและทำงานได้เสมอ
3 MP - ภาพถ่ายจริงขนาด 2048×1536 หน้าจอจะมืดลงประมาณสองวินาที นั่นคือไดรเวอร์กล้องกำลังรีสตาร์ท และจะอธิบายด้านล่าง
FLIP - สะท้อนภาพเมื่อคุณหันเลนส์ออกจากตัวเอง
ทำไมหน้าจอจึงมืดลงสำหรับการถ่ายภาพ 3 MP
รูปแบบพิกเซลของกล้องจะถูกกำหนดเมื่อไดรเวอร์เริ่มทำงาน คุณสามารถเปลี่ยนความละเอียดได้ทันที แต่คุณไม่สามารถสลับจาก RGB565 (สิ่งที่หน้าจอต้องการ) ไปเป็น JPEG (สิ่งที่ไฟล์ต้องการ) โดยไม่รีสตาร์ทไดรเวอร์ ดังนั้นเส้นทางความละเอียดสูงจึงเรียกใช้ esp_camera_deinit() รีสตาร์ทในโหมด JPEG ที่ความละเอียดเต็ม ถ่ายภาพ แล้วจึงคืนค่าวิวไฟน์เดอร์ การหยุดชั่วคราวนี้เป็นเรื่องปกติ ไม่ใช่บั๊ก
การประทับเวลาโดยไม่ต้องใช้อินเทอร์เน็ต
ภาพถ่ายจะถูกบันทึกเป็น /IMG_260730_143052.jpg - ปี เดือน วัน ชั่วโมง นาที วินาที ค่านั้นมาจากนาฬิกาเวลาจริง PCF8563T ซึ่งทำงานต่อเนื่องด้วยแบตเตอรี่ ดังนั้นเวลาจึงยังคงอยู่แม้ถอดปลั๊ก หากนาฬิกาหมดพลังงาน สเก็ตช์จะตั้งค่าเริ่มต้นจากช่วงเวลาที่สเก็ตช์ถูกคอมไพล์
การแก้ไขตัวอย่างจากผู้ผลิต
ตัวอย่างกล้องของผู้ขายเองกำหนดให้การ์ด SD อยู่บนพิน 2, 42 และ 41 หมายเลขพินเหล่านั้นคัดลอกมาจากบอร์ดอื่น - บนบอร์ดนี้它们是ไมโครโฟนพิน การ์ด SD จริงๆ แล้วอยู่บนบัส SPI ของจอแสดงผลพร้อมชิปเลือกของตัวเองที่ IO47 ซึ่งเป็นสิ่งที่โค้ดที่นี่ใช้ สิ่งนี้สำคัญหากคุณต้องการให้กล้องและไมโครโฟนทำงานพร้อมกันในสเก็ตช์เดียวกัน
การใช้บัส SPI ร่วมกัน
จอแสดงผลและการ์ด SD อยู่บนสายสามเส้นเดียวกัน ดังนั้นทั้งคู่ต้องผ่านไดรเวอร์ SPI เดียวกัน สเก็ตช์นี้ใช้ Arduino_HWSPI กับออบเจกต์ SPI มาตรฐานด้วยเหตุผลนั้นพอดี การใช้ไดรเวอร์ที่แตกต่างกันสำหรับแต่ละอุปกรณ์เป็นสาเหตุคลาสสิกของจอแสดงผลที่ค้างทันทีที่เมานต์การ์ด SD
เกี่ยวกับบอร์ด MaTouch AI ESP32-S3 2.8"
ทุกโปรเจกต์ในหน้านี้ทำงานบน MaTouch AI ESP32-S3 2.8" TFT ST7789V จาก Makerfabs เป็นบอร์ดแบบครบวงจร: หน้าจอสัมผัสสี กล้อง 3 เมกะพิกเซล ไมโครโฟนสองตัว และแอมพลิฟายเออร์ลำโพงจริง ทั้งหมดขับเคลื่อนด้วย ESP32-S3 พร้อม PSRAM 8 MB การผสมผสานนั้นทำให้โปรเจกต์ AI เหล่านี้เป็นไปได้บนบอร์ดเดียวโดยไม่ต้องเชื่อมต่ออุปกรณ์อื่นเพิ่มเติม
PSRAM 8 MB มีความสำคัญมากกว่าตัวเลขอื่นๆ ที่นี่ มันคือสิ่งที่ทำให้บอร์ดสามารถเก็บเฟรมกล้อง เสียงที่บันทึกไม่กี่วินาที หรือภาพที่เข้ารหัส base64 ในหน่วยความจำพร้อมกัน - ซึ่งไม่มีสิ่งใดพอดีกับ RAM ปกติของ ESP32
เอกสารผู้ผลิต: หน้า wiki ของ Makerfabs
ข้อมูลจำเพาะหลัก
โปรเซสเซอร์: ESP32-S3, dual core 240 MHz, WiFi 2.4 GHz + Bluetooth 5.0
หน่วยความจำ: แฟลช 16 MB, PSRAM 8 MB (จำเป็นสำหรับเกือบทุกโปรเจกต์ที่นี่)
จอแสดงผล: IPS 2.8", 320×240, ไดรเวอร์ ST7789V, SPI
ระบบสัมผัส: GT911 แบบ capacitive รองรับ 5 นิ้วพร้อมกัน
กล้อง: OV3660, 3 เมกะพิกเซล, สูงสุด 2048×1536
ไมโครโฟน: INMP441 I2S ดิจิทัลสองตัว (คู่สเตอริโอแท้)
ลำโพง: แอมพลิฟายเออร์คลาส D MAX98357A, 3.2 W ที่ 4 Ω
ที่เก็บข้อมูล: ช่องการ์ด microSD (โหมด SPI)
พลังงาน: USB-C, ขั้วต่อแบตเตอรี่ JST, เครื่องชาร์จ TP4056, สวิตช์เปิดปิด
อื่นๆ บนบอร์ด: ไฟ LED RGB WS2812B, นาฬิกาเวลาจริง PCF8563T ที่ใช้แบตเตอรี่สำรอง และเกจวัดแบตเตอรี่ MAX17048 ที่ไม่ได้ระบุไว้ในข้อมูลจำเพาะอย่างเป็นทางการ
พอร์ต USB-C ทั้งสองไม่เหมือนกัน ลำโพงของบอร์ดใช้พินสัญญาณร่วมกัน (IO19 และ IO20) กับพอร์ต USB เนทีฟ เนื่องจากพินเหล่านั้นคือสายข้อมูล USB ที่ถูกเดินสายตายตัวของ ESP32-S3 อัปโหลดและจ่ายไฟผ่าน พอร์ต USB-C CH340K เสมอ (พอร์ตที่อยู่ข้างปุ่ม RESET) และตั้งค่า USB CDC On Boot เป็น Disabled หากใช้พอร์ตผิด เสียงจะทำงานผิดปกติหรือการอัปโหลดจะล้มเหลว
การตั้งค่า Arduino IDE
การตั้งค่าเหล่านี้สำคัญ ปัญหาส่วนใหญ่ที่ผู้คนรายงานเกี่ยวกับบอร์ดนี้เกิดจากการตั้งค่าอย่างใดอย่างหนึ่งผิด และการตั้งค่าเหล่านี้จะรีเซ็ตเมื่อคุณเปลี่ยนเวอร์ชันคอร์ ดังนั้นควรตรวจสอบอีกครั้งหลังการเปลี่ยนแปลงใดๆ
การตั้งค่า | ค่า |
|---|---|
บอร์ด | ESP32S3 Dev Module |
เวอร์ชันคอร์ ESP32 | 2.0.17 |
PSRAM | OPI PSRAM |
ขนาดแฟลช | 16MB (128Mb) |
รูปแบบพาร์ติชัน | 16M Flash (3MB APP/9.9MB FATFS) |
USB CDC On Boot | Disabled |
ความเร็วอัปโหลด | 921600 |
ลบแฟลชทั้งหมดก่อนอัปโหลด | Disabled |
พอร์ต | พอร์ต USB-C CH340K |
ใช้ ESP32 core 2.0.17 ไม่ใช่ 3.x Espressif ได้ลบโมเดลตรวจจับใบหน้าบนอุปกรณ์ออกใน core 3 ดังนั้นโปรเจกต์เกี่ยวกับใบหน้าจะไม่สามารถคอมไพล์ได้ที่นั่น การกำหนดให้ใช้ 2.0.17 จะทำให้ทุกโปรเจกต์ในหน้านี้ทำงานได้ด้วยการตั้งค่าเดียว ใน Boards Manager เมนูเลือกเวอร์ชันจะให้คุณสลับไปมาได้ตามต้องการ
ใช้ GFX Library for Arduino เวอร์ชัน 1.5.6 ไม่ใช่ 1.6.x เวอร์ชัน 1.6 ถูกสร้างสำหรับ ESP32 core 3 และอาจค้างตอนเริ่มต้นบน core 2.0.17 หากหน้าจอยังคงเป็นสีดำหลังอัปโหลด นี่คือสิ่งแรกที่ควรตรวจสอบ
ไลบรารีที่จำเป็น
ติดตั้งผ่าน Tools → Manage Libraries ใน Arduino IDE หมายเลขเวอร์ชันมีความสำคัญ - กรุณาใช้เวอร์ชันที่ระบุไว้
ไลบรารี | เวอร์ชัน | ผู้เขียน |
|---|---|---|
GFX Library for Arduino | 1.5.6 | moononournation |
bb_captouch | 1.3.1 | Larry Bank |
การแก้ไขปัญหา
อาการ | สาเหตุและวิธีแก้ไข |
|---|---|
หน้าจอยังคงเป็นสีดำ | เวอร์ชัน GFX library ผิด (ใช้ 1.5.6) หรือการตั้งค่าบอร์ดผิด |
|
|
ไม่สามารถอัปโหลดได้ / ไม่มีพอร์ต COM | พอร์ต USB-C ผิด หรือไม่ได้ติดตั้งไดรเวอร์ CH340 |
กล้องล้มเหลวและไม่สามารถกู้คืนได้ | สายรีเซ็ตของกล้องเชื่อมต่อกับปุ่ม RESET ของบอร์ด ดังนั้นซอฟต์แวร์จึงไม่สามารถรีสตาร์ทได้ กด RESET หากยังล้มเหลว ให้เสียบสายแพของกล้องใหม่ |
ดาวน์โหลดโค้ด
สเก็ตช์ Arduino ที่สมบูรณ์สำหรับโปรเจกต์นี้ พร้อมด้วย pins.h และไฟล์อื่นๆ ที่จำเป็นทั้งหมด สามารถดาวน์โหลดได้ฟรี
แตกไฟล์ เปิดไฟล์ .ino ใน Arduino IDE ตรวจสอบการตั้งค่าด้านบน แล้วอัปโหลดผ่านพอร์ต USB-C CH340K
สถานที่ซื้อบอร์ดนี้
ลิงก์เหล่านี้เป็นลิงก์พันธมิตร (affiliate) คุณไม่ต้องจ่ายเพิ่ม และช่วยสนับสนุนโค้ดฟรีและบทเรียนบนเว็บไซต์นี้ - ขอบคุณ
This tutorial is part of: Makerfabs MaTouch AI ESP32S3 2.8" Camera
- Makerfabs MaTouch ESP32-S3 2.8 นิ้ว กล้อง การตรวจจับใบหน้าแบบออฟไลน์บน ESP32-S3 - ไม่ต้องใช้อินเทอร์เน็ต ไม่ต้องใช้คีย์ API
- เมกเกอร์แฟบส์ MaTouch ESP32-S3 2.8 นิ้ว กล้อง สร้างผู้ช่วยเสียง AI บน ESP32-S3 (Azure + DeepSeek)
- Makerfabs MaTouch ESP32-S3 2.8 นิ้ว กล้อง ESP32-S3 AI Vision: ชี้กล้องแล้วถามว่ามันเห็นอะไร
/*
* ===========================================================================
* 02_Camera_Touch — MaTouch AI ESP32-S3 2.8" TFT ST7789V
* ===========================================================================
*
* ---------------------------------------------------------------------------
----------
* ROBOJAX.COM - MaTouch AI ESP32-S3 2.8" project series
*
* WATCH THE VIDEO
* https://youtu.be/6AL3g3tC_Hk
*
* WRITTEN TUTORIALS - every project, with photos and full explanation
* Camera and touchscreen.... https://robojax.com/RTJ849
* Offline face recognition.. https://robojax.com/RTJ850
* AI voice assistant........ https://robojax.com/RTJ851
* AI vision................. https://robojax.com/RTJ852
*
* GET THE BOARD - SAVE $5 with coupon code: Robojax_Makerfab
* https://www.makerfabs.com/matouch-ai-esp32s3-2-8-tft-st7789v.html
* (enter the code at checkout)
*
* All of this code is free. If it helped you, a subscribe on YouTube is
* the best way to support more of it.
*
* Watching the video first will save you time - it shows the Arduino IDE
* settings and the library versions being set up step by step.
* ---------------------------------------------------------------------------
*
* Live camera viewfinder with a touch interface, saving real photos to the
* microSD card, each one stamped with the true date and time from the board's
* battery-backed RTC.
*
* Three buttons down the right hand side:
*
* SNAP quick save. Converts the frame already on screen to JPEG.
* Instant, 240x240, always works.
*
* 3 MP the real thing. Tears the camera down, brings it back up at
* 2048x1536 JPEG, takes the shot, and restores the viewfinder.
* Takes a couple of seconds. This is the OV3660's full
* 3 megapixels, not an upscale.
*
* MIRROR flips the image, for when you turn the board around to point
* the lens at someone else.
*
* Photos land on the SD card as /IMG_260729_143052.jpg
*
* No WiFi, no API keys, no internet.
*
* ---------------------------------------------------------------------------
* WHY THE 3 MP BUTTON HAS TO WORK SO HARD
* A camera's pixel format is fixed when the driver starts. You can change the
* resolution on the fly, but you cannot switch RGB565 (what the screen wants)
* to JPEG (what the SD card wants) without restarting the driver. So the
* hi-res path deinitialises and reinitialises. That is normal, and it is why
* the screen goes black for a moment.
*
* ---------------------------------------------------------------------------
* BOARD SETTINGS - see pins.h. In short: ESP32S3 Dev Module, core 2.0.17,
* OPI PSRAM, 16MB flash, 16M partition scheme, upload via the CH340K port.
*
* LIBRARIES
* GFX Library for Arduino v1.5.6
* bb_captouch v1.3.1
*
* ---------------------------------------------------------------------------
* FUNCTIONS IN THIS SKETCH
* rtcRead() / rtcWrite(...) read/set the PCF8563 battery-backed clock
* rtcSeedFromBuildTime() set the clock from compile time if it lost power
* getTouch(&x,&y) read the touch panel, mapped to screen coordinates
* inBox(...) hit-test a touch against a button rectangle
* camStart(...) start the camera in a given format/size
* camStartPreview() start the 240x240 RGB565 live-view camera
* makeFilename(...) /IMG_YYMMDD_HHMMSS.jpg from the RTC time
* writeToSD(...) save a buffer to the SD card
* snapQuick() SNAP button: re-encode the preview frame to JPEG
* snapHiRes() 3MP button: restart camera in JPEG mode, full res
* drawButton(...) / drawButtons() / drawStatus() the touch UI
* setup() / loop() boot / viewfinder + edge-detected button taps
*
* Robojax.com
* ===========================================================================
*/
#include <Arduino_GFX_Library.h>
#include <bb_captouch.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include "esp_camera.h"
#include "img_converters.h" // frame2jpg()
#include "pins.h"
/* The TFT and SD share pins 13/12/48, so both must go through the SAME SPI
* peripheral: Arduino_HWSPI wraps the standard SPI object the SD library
* uses. (Arduino_ESP32SPI would grab a different peripheral, and the display
* goes dark the moment the SD card claims the pins.) */
Arduino_HWSPI *bus = new Arduino_HWSPI(
TFT_DC, TFT_CS, TFT_SCLK, TFT_MOSI, TFT_MISO, &SPI, true /* shared bus */);
Arduino_GFX *gfx = new Arduino_ST7789(bus, TFT_RES, 1, true);
BBCapTouch bbct;
/* --- layout: 240x240 viewfinder on the left, 80px button column on the right */
#define VIEW_W 240
#define BTN_X 242
#define BTN_W 78
#define BTN_SNAP_Y 2
#define BTN_HIRES_Y 62
#define BTN_MIRROR_Y 122
#define BTN_H 56
#define STATUS_Y 186
bool ok_sd = false;
bool ok_rtc = false;
bool mirrored = true; // the lens is upside down relative to the screen
int shot_count = 0;
char status[48] = "ready";
/* --- the RTC time structure ------------------------------------------------
* Defined up here, before any function, with explicit prototypes below.
* WHY: the Arduino IDE auto-generates function prototypes and can insert them
* ABOVE this struct - a function returning RtcTime then fails to compile with
* "RtcTime does not name a type". Writing the prototypes ourselves stops the
* IDE generating its own, so the order stays correct.
* ------------------------------------------------------------------------- */
struct RtcTime {
int year, mon, day, hour, min, sec;
bool valid;
};
// explicit prototypes - do not remove (see comment above)
RtcTime rtcRead();
void rtcWrite(int year, int mon, int day, int hour, int min, int sec);
void rtcSeedFromBuildTime();
bool getTouch(uint16_t *x, uint16_t *y);
bool camStart(pixformat_t fmt, framesize_t size, int fb_count, int quality);
bool camStartPreview();
void makeFilename(char *out, size_t n);
bool writeToSD(const char *path, const uint8_t *data, size_t len);
void snapQuick();
void snapHiRes();
void drawButton(int y, const char *label, uint16_t colour);
void drawButtons();
void drawStatus();
/* ===========================================================================
* RTC — PCF8563T
* =========================================================================== */
static inline uint8_t bcd2dec(uint8_t b) { return (uint8_t)((b >> 4) * 10 + (b & 0x0F)); }
static inline uint8_t dec2bcd(uint8_t d) { return (uint8_t)(((d / 10) << 4) | (d % 10)); }
RtcTime rtcRead() {
RtcTime t = {2026, 1, 1, 0, 0, 0, false};
Wire.beginTransmission(ADDR_PCF8563);
Wire.write(0x02);
if (Wire.endTransmission(false) != 0) return t;
if (Wire.requestFrom((int)ADDR_PCF8563, 7) != 7) return t;
uint8_t r[7];
for (uint8_t i = 0; i < 7; i++) r[i] = Wire.read();
t.valid = !(r[0] & 0x80); // bit 7 = Voltage Low: time is unreliable
t.sec = bcd2dec(r[0] & 0x7F);
t.min = bcd2dec(r[1] & 0x7F);
t.hour = bcd2dec(r[2] & 0x3F);
t.day = bcd2dec(r[3] & 0x3F);
t.mon = bcd2dec(r[5] & 0x1F);
t.year = bcd2dec(r[6]) + 2000;
return t;
}
void rtcWrite(int year, int mon, int day, int hour, int min, int sec) {
Wire.beginTransmission(ADDR_PCF8563);
Wire.write(0x02);
Wire.write(dec2bcd(sec)); // writing clears the Voltage Low flag
Wire.write(dec2bcd(min));
Wire.write(dec2bcd(hour));
Wire.write(dec2bcd(day));
Wire.write(0x00); // weekday - we do not use it
Wire.write(dec2bcd(mon)); // century bit left at 0 => 20xx
Wire.write(dec2bcd(year % 100));
Wire.endTransmission();
}
/* If the RTC has lost power, seed it from the moment this sketch was compiled.
* Close enough for photo timestamps, and it means the clock is never blank. */
void rtcSeedFromBuildTime() {
const char *mon_names = "JanFebMarAprMayJunJulAugSepOctNovDec";
char mon_str[4] = {0};
int day, year, hour, min, sec;
sscanf(__DATE__, "%3s %d %d", mon_str, &day, &year);
sscanf(__TIME__, "%d:%d:%d", &hour, &min, &sec);
int mon = 1;
for (int i = 0; i < 12; i++)
if (strncmp(mon_str, mon_names + i * 3, 3) == 0) { mon = i + 1; break; }
rtcWrite(year, mon, day, hour, min, sec);
Serial.printf("RTC seeded from build time: %04d-%02d-%02d %02d:%02d:%02d\n",
year, mon, day, hour, min, sec);
}
/* ===========================================================================
* Touch
* =========================================================================== */
bool getTouch(uint16_t *x, uint16_t *y) {
TOUCHINFO ti;
if (!bbct.getSamples(&ti)) return false;
if (ti.count < 1) return false;
*x = ti.y[0];
*y = (ti.x[0] > 240) ? 0 : (240 - ti.x[0]);
return true;
}
static inline bool inBox(uint16_t x, uint16_t y, int bx, int by, int bw, int bh) {
return (x >= bx && x < bx + bw && y >= by && y < by + bh);
}
/* ===========================================================================
* Camera
* =========================================================================== */
bool camStart(pixformat_t fmt, framesize_t size, int fb_count, int quality) {
camera_config_t c;
c.ledc_channel = LEDC_CHANNEL_0;
c.ledc_timer = LEDC_TIMER_0;
c.pin_d0 = CAM_PIN_D0; c.pin_d1 = CAM_PIN_D1;
c.pin_d2 = CAM_PIN_D2; c.pin_d3 = CAM_PIN_D3;
c.pin_d4 = CAM_PIN_D4; c.pin_d5 = CAM_PIN_D5;
c.pin_d6 = CAM_PIN_D6; c.pin_d7 = CAM_PIN_D7;
c.pin_xclk = CAM_PIN_XCLK;
c.pin_pclk = CAM_PIN_PCLK;
c.pin_vsync = CAM_PIN_VSYNC;
c.pin_href = CAM_PIN_HREF;
/* Share the I2C bus Wire already drives (touch/RTC live there too) instead
* of letting the camera install a second driver on the same pins - that
* kills touch and the RTC. Wire.begin() must run before this function. */
c.pin_sccb_sda = -1;
c.pin_sccb_scl = -1;
c.sccb_i2c_port = 0; // Wire = I2C port 0
c.pin_pwdn = CAM_PIN_PWDN;
c.pin_reset = CAM_PIN_RESET;
c.xclk_freq_hz = 20000000;
c.frame_size = size;
c.pixel_format = fmt;
c.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
c.fb_location = CAMERA_FB_IN_PSRAM;
c.jpeg_quality = quality;
c.fb_count = fb_count;
esp_err_t err = esp_camera_init(&c);
if (err != ESP_OK) {
Serial.printf("esp_camera_init failed: 0x%x\n", err);
return false;
}
sensor_t *s = esp_camera_sensor_get();
if (s) {
s->set_hmirror(s, mirrored ? 1 : 0);
s->set_vflip(s, mirrored ? 1 : 0);
s->set_brightness(s, 1);
s->set_saturation(s, 0);
}
return true;
}
bool camStartPreview() {
if (camStart(PIXFORMAT_RGB565, FRAMESIZE_240X240, 2, 12)) return true;
/* Retry with ONE frame buffer. The camera's DMA buffer must live in
* internal RAM, and if something else grabbed it first this is the
* difference between working and "cam_dma_config failed". */
Serial.println("camera: retrying with a single frame buffer (low DMA RAM)");
return camStart(PIXFORMAT_RGB565, FRAMESIZE_240X240, 1, 12);
}
/* ===========================================================================
* Saving
* =========================================================================== */
void makeFilename(char *out, size_t n) {
RtcTime t = rtcRead();
snprintf(out, n, "/IMG_%02d%02d%02d_%02d%02d%02d.jpg",
t.year % 100, t.mon, t.day, t.hour, t.min, t.sec);
}
bool writeToSD(const char *path, const uint8_t *data, size_t len) {
if (!ok_sd) { snprintf(status, sizeof(status), "no SD card"); return false; }
File f = SD.open(path, FILE_WRITE);
if (!f) { snprintf(status, sizeof(status), "SD open failed"); return false; }
size_t written = f.write(data, len);
f.close();
if (written != len) { snprintf(status, sizeof(status), "SD write short"); return false; }
Serial.printf("Saved %s (%u bytes)\n", path, (unsigned)len);
return true;
}
/* Quick save: re-encode the RGB565 frame already in hand. */
void snapQuick() {
snprintf(status, sizeof(status), "saving...");
drawStatus();
camera_fb_t *fb = esp_camera_fb_get();
if (!fb) { snprintf(status, sizeof(status), "no frame"); return; }
uint8_t *jpg = NULL;
size_t jpg_len = 0;
bool ok = frame2jpg(fb, 90, &jpg, &jpg_len);
esp_camera_fb_return(fb);
if (!ok || !jpg) { snprintf(status, sizeof(status), "jpeg encode failed"); return; }
char path[32];
makeFilename(path, sizeof(path));
if (writeToSD(path, jpg, jpg_len)) {
shot_count++;
snprintf(status, sizeof(status), "%s %uKB", path + 1, (unsigned)(jpg_len / 1024));
}
free(jpg);
}
/* Full resolution: restart the driver in JPEG mode at 2048x1536. */
void snapHiRes() {
snprintf(status, sizeof(status), "3MP: restarting cam");
drawStatus();
gfx->fillRect(0, 0, VIEW_W, 240, BLACK);
gfx->setTextColor(YELLOW);
gfx->setCursor(60, 110);
gfx->print("capturing 3 MP...");
esp_camera_deinit();
delay(120);
if (!camStart(PIXFORMAT_JPEG, FRAMESIZE_QXGA, 1, 10)) {
snprintf(status, sizeof(status), "3MP init failed");
delay(120);
if (!camStartPreview()) snprintf(status, sizeof(status), "CAM DEAD - press RESET");
return;
}
// Throw away the first few frames so exposure and white balance settle,
// otherwise the hi-res shot comes out darker than the viewfinder.
for (int i = 0; i < 3; i++) {
camera_fb_t *w = esp_camera_fb_get();
if (w) esp_camera_fb_return(w);
delay(120);
}
camera_fb_t *fb = esp_camera_fb_get();
if (fb && fb->len > 0) {
char path[32];
makeFilename(path, sizeof(path));
if (writeToSD(path, fb->buf, fb->len)) {
shot_count++;
snprintf(status, sizeof(status), "3MP %ux%u %uKB",
fb->width, fb->height, (unsigned)(fb->len / 1024));
}
} else {
snprintf(status, sizeof(status), "3MP capture failed");
}
if (fb) esp_camera_fb_return(fb);
// Back to the viewfinder
esp_camera_deinit();
delay(120);
if (!camStartPreview()) {
snprintf(status, sizeof(status), "CAM DEAD - press RESET");
gfx->fillRect(0, 0, VIEW_W, 240, RED);
}
drawButtons();
}
/* ===========================================================================
* UI
* =========================================================================== */
void drawButton(int y, const char *label, uint16_t colour) {
gfx->fillRoundRect(BTN_X, y, BTN_W, BTN_H, 6, colour);
gfx->drawRoundRect(BTN_X, y, BTN_W, BTN_H, 6, WHITE);
gfx->setTextSize(2);
gfx->setTextColor(WHITE);
int len = strlen(label);
gfx->setCursor(BTN_X + (BTN_W - len * 12) / 2, y + (BTN_H - 16) / 2);
gfx->print(label);
gfx->setTextSize(1);
}
void drawButtons() {
drawButton(BTN_SNAP_Y, "SNAP", gfx->color565(0, 90, 160));
drawButton(BTN_HIRES_Y, "3 MP", gfx->color565(140, 70, 0));
drawButton(BTN_MIRROR_Y, "FLIP", gfx->color565(80, 80, 90));
}
void drawStatus() {
gfx->fillRect(BTN_X, STATUS_Y, BTN_W, 240 - STATUS_Y, BLACK);
gfx->setTextColor(CYAN);
gfx->setTextSize(1);
gfx->setCursor(BTN_X + 2, STATUS_Y + 2);
gfx->printf("shots:%d", shot_count);
RtcTime t = rtcRead();
gfx->setCursor(BTN_X + 2, STATUS_Y + 14);
gfx->setTextColor(ok_rtc ? GREEN : RED);
gfx->printf("%02d:%02d:%02d", t.hour, t.min, t.sec);
// Status text wraps across the narrow column, 13 characters at a time
gfx->setTextColor(YELLOW);
int row = 0;
for (size_t i = 0; i < strlen(status) && row < 3; i += 13, row++) {
char chunk[14] = {0};
strncpy(chunk, status + i, 13);
gfx->setCursor(BTN_X + 2, STATUS_Y + 26 + row * 10);
gfx->print(chunk);
}
}
/* ===========================================================================
* SETUP
* =========================================================================== */
void setup() {
Serial.begin(115200);
delay(400);
Serial.println("\n=== 02 Camera + Touch | Robojax.com ===");
pinMode(TFT_BLK, OUTPUT);
digitalWrite(TFT_BLK, LOW);
pinMode(SD_CS, OUTPUT);
digitalWrite(SD_CS, HIGH);
// one shared SPI bus for TFT + SD, started before either device
SPI.begin(TFT_SCLK, TFT_MISO, TFT_MOSI);
gfx->begin();
gfx->fillScreen(BLACK);
gfx->setTextSize(1);
digitalWrite(TFT_BLK, HIGH);
gfx->setTextColor(CYAN);
gfx->setCursor(4, 4);
gfx->print("MaTouch AI - camera demo (Robojax.com)");
// Touch. Pulses IO18, which is also the LCD reset - harmless at this point.
bbct.init(TOUCH_SDA, TOUCH_SCL, TOUCH_RST, TOUCH_INT);
delay(50);
// RTC on the shared I2C bus (must be up before the camera - shared SCCB)
Wire.begin(I2C_SDA, I2C_SCL, 100000);
delay(20);
RtcTime t = rtcRead();
ok_rtc = (t.year >= 2020);
if (ok_rtc && !t.valid) {
Serial.println("RTC lost power - seeding from build time");
rtcSeedFromBuildTime();
}
gfx->setCursor(4, 16);
gfx->setTextColor(ok_rtc ? GREEN : RED);
gfx->print(ok_rtc ? "RTC ok" : "RTC not found");
/* ---- CAMERA BEFORE SD. This order matters. ----
* The camera's DMA buffer must come from internal (not PSRAM) memory, and
* mounting the SD card first can take enough of it that the camera driver
* fails with "cam_dma_config failed / 0xffffffff". Camera first, SD second,
* and both fit. (On ESP32 core 3.x the allocation is looser and the old
* order happened to work - which is why this only appeared on 2.0.17.) */
gfx->setCursor(4, 28);
gfx->setTextColor(YELLOW);
gfx->print("starting camera...");
Serial.printf("free internal RAM before camera: %u bytes\n",
(unsigned)heap_caps_get_free_size(MALLOC_CAP_INTERNAL));
if (!camStartPreview()) {
gfx->fillScreen(RED);
gfx->setTextColor(WHITE);
gfx->setTextSize(2);
gfx->setCursor(20, 90);
gfx->print("CAMERA FAILED");
gfx->setTextSize(1);
gfx->setCursor(20, 120);
gfx->print("0xffffffff / cam_dma_config = out of DMA memory:");
gfx->setCursor(20, 132);
gfx->print(" check Tools > PSRAM = OPI PSRAM, then press RESET.");
gfx->setCursor(20, 150);
gfx->print("Any other error = reseat the camera FPC cable.");
gfx->setCursor(20, 162);
gfx->print("The camera reset line is tied to board RESET, so it");
gfx->setCursor(20, 174);
gfx->print("cannot be restarted from software - use the button.");
while (1) delay(1000);
}
// SD card LAST. Same three wires as the display, its own chip select on IO47.
const uint32_t speeds[] = {40000000, 20000000, 10000000, 4000000};
for (uint8_t i = 0; i < 4 && !ok_sd; i++)
if (SD.begin(SD_CS, SPI, speeds[i])) ok_sd = true;
digitalWrite(SD_CS, HIGH);
gfx->setCursor(4, 40);
gfx->setTextColor(ok_sd ? GREEN : RED);
gfx->print(ok_sd ? "SD ok" : "SD FAILED - photos will not save");
Serial.println(ok_sd ? "SD ok" : "SD failed");
delay(600);
gfx->fillScreen(BLACK);
drawButtons();
drawStatus();
Serial.println("Running. SNAP = quick, 3 MP = full resolution, FLIP = mirror.");
}
/* ===========================================================================
* LOOP
* =========================================================================== */
void loop() {
static uint32_t last_status = 0;
/* touch edge detection: a button fires exactly once per physical tap.
* touch_down latches on the first touched frame and only releases after
* several consecutive untouched frames (the GT911 occasionally skips a
* sample mid-press, which must not count as a release). */
static bool touch_down = false;
static uint8_t release_count = 0;
/* FPS: averaged over half a second and redrawn only then - redrawing it
* every frame just makes it flicker unreadably. */
static uint32_t fps_window_start = 0;
static uint16_t fps_frames = 0;
// --- viewfinder ---
camera_fb_t *fb = esp_camera_fb_get();
if (fb) {
gfx->draw16bitBeRGBBitmap(0, 0, (uint16_t *)fb->buf, fb->width, fb->height);
esp_camera_fb_return(fb);
fps_frames++;
}
// --- touch: act only on the moment the finger first lands ---
uint16_t x, y;
if (getTouch(&x, &y)) {
release_count = 0;
if (!touch_down) { // new tap, not a held finger
touch_down = true;
if (inBox(x, y, BTN_X, BTN_SNAP_Y, BTN_W, BTN_H)) {
snapQuick();
drawStatus();
} else if (inBox(x, y, BTN_X, BTN_HIRES_Y, BTN_W, BTN_H)) {
snapHiRes();
drawStatus();
} else if (inBox(x, y, BTN_X, BTN_MIRROR_Y, BTN_W, BTN_H)) {
mirrored = !mirrored;
sensor_t *s = esp_camera_sensor_get();
if (s) { s->set_hmirror(s, mirrored ? 1 : 0); s->set_vflip(s, mirrored ? 1 : 0); }
snprintf(status, sizeof(status), mirrored ? "mirrored" : "normal");
drawStatus();
}
}
} else if (touch_down) {
// require ~4 consecutive empty reads (~130ms) before declaring release
if (++release_count >= 4) { touch_down = false; release_count = 0; }
}
// --- FPS, averaged, top left corner of the viewfinder ---
uint32_t now = millis();
if (now - fps_window_start >= 500) {
float fps = fps_frames * 1000.0f / (now - fps_window_start);
fps_window_start = now;
fps_frames = 0;
gfx->fillRect(2, 2, 56, 10, BLACK);
gfx->setTextColor(GREEN);
gfx->setTextSize(1);
gfx->setCursor(4, 3);
gfx->printf("FPS %.1f", fps);
}
if (now - last_status > 1000) { last_status = now; drawStatus(); }
}
สิ่งที่คุณอาจต้องการ
-
อื่น ๆProduct page for MaTouch AI ESP32S3 2.8" TFT ST7789Vmakerfabs.com
แหล่งข้อมูลและเอกสารอ้างอิง
-
เอกสารMakerfabs MaTouch ESP32-S3 2.8" Camera and Touchscreen: user's manualwiki.makerfabs.com
-
เอกสาร
-
เอกสารProduct page for MaTouch AI ESP32S3 2.8" TFT ST7789Vmakerfabs.com
-
ดาวน์โหลดArduino GFX Library on Githubgithub.com
ไฟล์📁
ไฟล์ที่ต้องการ (.h)
ไฟล์อื่น ๆ
แผนภาพวงจร
-
MaTouch_AI 2.8“ MaTouch AI ESP32S3 2.8" TFT ST7789V schematicThe latest MaTouch AI board integrate I2S voice input/I2S speaker/ 3 million camera OV3660/ 320*240 resolution display, with ESP32S3 strong processor& Wifi ability, to make this board a good tool/platform for AI development with ESP32.
MaTouch_AI 2.8“ SPI TFT ST7789V V1.1.PDF0.15 MB