یہ ٹیوٹوریل کا حصہ ہے: Makerfabs MaTouch AI ESP32S3 2.8" کیمرہ
تازہ ترین MaTouch AI بورڈ میں I2S وائس ان پٹ/I2S اسپیکر/ 3 ملین پکسل کیمرہ OV3660/ 320*240 ریزولوشن ڈسپلے شامل ہے، اور ESP32S3 کے مضبوط پروسیسر اور وائی فائی صلاحیت کے ساتھ، یہ بورڈ ESP32 کے ساتھ AI ڈیولپمنٹ کے لیے ایک بہترین ٹول/پلیٹ فارم بناتا ہے۔
Makerfabs MaTouch ESP32-S3 2.8" کیمرہ اور ٹچ اسکرین: ایس ڈی کارڈ پر 3MP تصاویر
لائیو ویو فائنڈر، 3 میگا پکسل تصاویر، حقیقی ٹائم اسٹیمپ کے ساتھ SD میں محفوظ
ایک کام کرنے والی کیمرہ ایپ: تقریباً 25 فریم فی سیکنڈ پر لائیو ویو فائنڈر، تین ٹچ بٹن، اور تصاویر مائیکرو SD کارڈ میں محفوظ ہوتی ہیں جن پر بورڈ کی بیٹری سے چلنے والی گھڑی سے درست تاریخ اور وقت لگا ہوتا ہے۔
کیمرہ اور ٹچ اسکرین 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 ڈرائیور سے گزرنا ہوگا۔ یہ اسکیچ اسی وجہ سے معیاری SPI آبجیکٹ کے ساتھ Arduino_HWSPI استعمال کرتا ہے۔ ہر ایک کے لیے مختلف ڈرائیور استعمال کرنا اس کلاسک مسئلے کا سبب ہے جہاں SD کارڈ لگتے ہی ڈسپلے منجمد ہو جاتا ہے۔
MaTouch AI ESP32-S3 2.8" بورڈ کے بارے میں
اس صفحے کے ہر پروجیکٹ Makerfabs کے MaTouch AI ESP32-S3 2.8" TFT ST7789V پر چلتا ہے۔ یہ ایک آل ان ون بورڈ ہے: ایک رنگین ٹچ اسکرین، ایک 3 میگا پکسل کیمرہ، دو مائیکروفون اور ایک حقیقی اسپیکر ایمپلیفائر، سب ایک ESP32-S3 کے ذریعے چلتے ہیں جس میں 8 MB PSRAM ہے۔ یہ مجموعہ ہی ان AI پروجیکٹس کو ایک ہی بورڈ پر ممکن بناتا ہے جس میں کچھ اور منسلک نہ ہو۔
8 MB PSRAM یہاں کسی بھی دوسرے نمبر سے زیادہ اہم ہے۔ یہ بورڈ کو ایک ہی وقت میں کیمرہ فریم، چند سیکنڈ کی ریکارڈ شدہ آڈیو، یا بیس64-انکوڈڈ تصویر میموری میں رکھنے کی اجازت دیتا ہے - ان میں سے کوئی بھی ESP32 کی عام RAM میں فٹ نہیں ہوتا۔
مینوفیکچرر دستاویزات: Makerfabs ویکی صفحہ۔
اہم خصوصیات
پروسیسر: ESP32-S3، ڈوئل کور 240 MHz، WiFi 2.4 GHz + Bluetooth 5.0
میموری: 16 MB فلیش، 8 MB PSRAM (یہاں تقریباً ہر پروجیکٹ کے لیے ضروری)
ڈسپلے: 2.8" IPS، 320×240، ST7789V ڈرائیور، SPI
ٹچ: GT911 کیپسیٹو، ایک ساتھ 5 انگلیوں کو ٹریک کرتا ہے
کیمرہ: OV3660، 3 میگا پکسل، 2048×1536 تک
مائیکروفون: دو INMP441 I2S ڈیجیٹل مائیک (ایک حقیقی سٹیریو جوڑا)
اسپیکر: MAX98357A کلاس-D ایمپلیفائر، 4 Ω پر 3.2 W
اسٹوریج: مائیکرو SD کارڈ سلاٹ (SPI موڈ)
پاور: USB-C، JST بیٹری کنیکٹر، TP4056 چارجر، پاور سوئچ
بورڈ پر بھی: WS2812B RGB LED، PCF8563T بیٹری سے چلنے والی ریئل ٹائم کلاک، اور ایک MAX17048 بیٹری فیول گیج جو سرکاری خصوصیات میں درج نہیں ہے
دو USB-C پورٹ ایک جیسے نہیں ہیں۔ بورڈ کا اسپیکر اپنے سگنل پن (IO19 اور IO20) کو نیٹیو USB پورٹ کے ساتھ شیئر کرتا ہے، کیونکہ یہ پن ESP32-S3 کی ہارڈ وائرڈ USB ڈیٹا لائنیں ہیں۔ ہمیشہ CH340K USB-C پورٹ (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 |
پورٹ | CH340K USB-C پورٹ |
ESP32 کور 2.0.17 استعمال کریں، 3.x نہیں۔ Espressif نے کور 3 میں ڈیوائس پر چہرہ شناخت کرنے والے ماڈلز ہٹا دیے ہیں، اس لیے چہرے کے پروجیکٹ وہاں کمپائل نہیں ہوں گے۔ 2.0.17 کو مقرر رکھنے سے اس صفحے کے ہر پروجیکٹ ایک کنفیگریشن کے ساتھ کام کرتے رہتے ہیں۔ بورڈز مینیجر میں، ورژن ڈراپ ڈاؤن آپ کو جب چاہیں آگے پیچھے سوئچ کرنے دیتا ہے۔
GFX لائبریری برائے Arduino ورژن 1.5.6 استعمال کریں، 1.6.x نہیں۔ 1.6 ریلیزز ESP32 کور 3 کے لیے بنائی گئی ہیں اور کور 2.0.17 پر شروع ہوتے وقت رک سکتی ہیں۔ اگر اپ لوڈ کرنے کے بعد آپ کی اسکرین سیاہ رہتی ہے، تو یہ سب سے پہلے چیک کرنے والی چیز ہے۔
مطلوبہ لائبریریاں
انہیں Arduino IDE میں Tools → Manage Libraries کے ذریعے انسٹال کریں۔ ورژن نمبر اہم ہیں - براہ کرم درج شدہ نمبرز استعمال کریں۔
لائبریری | ورژن | مصنف |
|---|---|---|
GFX لائبریری برائے Arduino | 1.5.6 | moononournation |
bb_captouch | 1.3.1 | Larry Bank |
مسائل کا حل
علامت | وجہ اور حل |
|---|---|
اسکرین سیاہ رہتی ہے | غلط GFX لائبریری ورژن (1.5.6 استعمال کریں) یا غلط بورڈ سیٹنگز۔ |
|
|
کچھ اپ لوڈ نہیں ہوتا / کوئی COM پورٹ نہیں | غلط USB-C پورٹ، یا CH340 ڈرائیور انسٹال نہیں ہے۔ |
کیمرہ ناکام ہو جاتا ہے اور کبھی بحال نہیں ہوتا | کیمرے کی ری سیٹ لائن بورڈ کے RESET بٹن سے منسلک ہے، اس لیے سافٹ ویئر اسے دوبارہ شروع نہیں کر سکتا۔ RESET دبائیں۔ اگر یہ پھر بھی ناکام ہو، تو کیمرے کی ربن کیبل کو دوبارہ لگائیں۔ |
کوڈ ڈاؤن لوڈ کریں
اس پروجیکٹ کے لیے مکمل Arduino اسکیچ، pins.h اور اس کی ضرورت کی ہر چیز کے ساتھ، مفت ڈاؤن لوڈ کے لیے دستیاب ہے۔
اسے ان زپ کریں، Arduino IDE میں .ino فائل کھولیں، اوپر دی گئی سیٹنگز چیک کریں، اور CH340K USB-C پورٹ کے ذریعے اپ لوڈ کریں۔
یہ بورڈ کہاں سے خریدیں
یہ ایفیلی ایٹ لنکس ہیں۔ ان سے آپ کو کوئی اضافی قیمت نہیں لگتی اور یہ اس سائٹ پر مفت کوڈ اور ٹیوٹوریلز کی حمایت کرتے ہیں - شکریہ۔
یہ ٹیوٹوریل کا حصہ ہے: Makerfabs MaTouch AI ESP32S3 2.8" کیمرہ
- Makerfabs MaTouch ESP32-S3 2.8" کیمرہ آف لائن چہرے کی شناخت ESP32-S3 پر - کوئی انٹرنیٹ نہیں، کوئی API کلید نہیں
- میکر فیبس ما ٹچ ESP32-S3 2.8 انچ کیمرہ ESP32-S3 پر AI وائس اسسٹنٹ بنائیں (Azure + DeepSeek)
- Makerfabs MaTouch ESP32-S3 2.8" کیمرہ ESP32-S3 AI ویژن: کیمرہ اشارہ کریں اور پوچھیں کہ یہ کیا دیکھتا ہے
/*
* ===========================================================================
* 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