Wannan koyawa wani ɓangare ne na: Makerfabs MaTouch AI ESP32S3 2.8" Camera
Sabuwar hukumar MaTouch AI ta haɗa shigar da murya ta I2S/lasifika ta I2S/kyamara mai miliyan 3 OV3660/nuni mai ƙuduri 320*240, tare da ƙaƙƙarfan processor na ESP32S3 da ikon Wifi, don sanya wannan hukumar ta zama kayan aiki mai kyau/dandamali don haɓaka AI tare da ESP32.
Makerfabs MaTouch ESP32-S3 2.8" Kamara da Fuskar Taɓawa: Hotuna 3MP zuwa Katin SD
Duburarar rayuwa, hotuna megapixel 3, adana zuwa SD tare da ainihin lokaci
Ƙa'idar kyamara mai aiki: duburarar rayuwa a kusan firam 25 a sakan daya, maɓallan taɓawa guda uku, da hotuna da aka adana zuwa katin microSD tare da kwanan wata da lokaci daidai daga agogon da ke da baturi a kan allo.
Kyamara da Fuskar Taɓawa suna aiki akan MaTouch AI ESP32-S3 board
Maɓallan uku
SNAP - hoto nan take a ƙudurin duburarar. Sauri kuma koyaushe yana aiki.
3 MP - ainihin hoto 2048×1536. Allon yana yin duhu na kusan daƙiƙa biyu; wannan shine direban kyamara yake sake farawa, kuma an bayyana shi a ƙasa.
FLIP - yana madubi hoton lokacin da ka nuna ruwan tabarau daga kanka.
Dalilin da yasa allon yake yin duhu don hoton 3 MP
Tsarin pixel na kyamara yana kafaffe lokacin da direban ya fara. Kana iya canza ƙuduri a kan tafiya, amma ba za ka iya canzawa daga RGB565 (abin da allon yake buƙata) zuwa JPEG (abin da fayil ɗin yake buƙata) ba tare da sake farawa direban ba. Don haka hanyar babban ƙuduri tana kira esp_camera_deinit(), tana sake farawa a yanayin JPEG a cikakken ƙuduri, ta ɗauki hoton, sannan ta mayar da duburarar. Tsayawar gaskiya ce, ba kuskure ba.
Lokutan ba tare da intanet ba
Ana adana hotuna azaman /IMG_260730_143052.jpg - shekara, wata, rana, awa, minti, sakan. Wannan ya fito daga agogon ainihin lokaci PCF8563T, wanda baturi ke ci gaba da aiki, don haka lokacin yana tsira daga an cire wutar. Idan agogon ya rasa wutar, sketch ɗin yana shuka shi daga lokacin da aka haɗa sketch ɗin.
Gyara ga misalin mai ƙera
Misalin kyamara na mai siyarwa kansa yana ayyana katin SD akan pins 2, 42 da 41. Waɗannan lambobin pin an kwafi su daga wani allo daban - a kan wannan su ne pins na makirifo. Katin SD a zahiri yana kan bas ɗin SPI na nuni tare da nasa zaɓin chip akan IO47, wanda shine abin da lambar nan ke amfani da shi. Wannan yana da muhimmanci idan kana son kyamara da makirifo suyi aiki a cikin sketch ɗaya.
Raba bas ɗin SPI ɗaya
Nuni da katin SD suna kan wayoyi uku iri ɗaya, don haka duka biyu dole su bi ta direban SPI ɗaya. Wannan sketch yana amfani da Arduino_HWSPI tare da daidaitaccen abu SPI don ainihin wannan dalili. Amfani da direba daban don kowanne shine sanannen dalilin nuni da ke daskarewa lokacin da aka ɗora katin SD.
Game da MaTouch AI ESP32-S3 2.8" board
Kowane aiki a wannan shafin yana aiki akan MaTouch AI ESP32-S3 2.8" TFT ST7789V daga Makerfabs. Allo ne gaba ɗaya: fuskar taɓawa mai launi, kyamara megapixel 3, makirifo biyu da amplifier mai magana na gaske, duk ana sarrafa su ta ESP32-S3 tare da 8 MB na PSRAM. Wannan haɗin shine abin da ke sa waɗannan ayyukan AI su yiwu akan allo ɗaya ba tare da wani abu da aka haɗa ba.
8 MB na PSRAM yana da muhimmanci fiye da kowane lamba a nan. Shi ne abin da ke ba allo damar riƙe firam ɗin kyamara, ƴan daƙiƙan sauti da aka rubuta, ko hoton da aka ɓoye base64 a cikin ƙwaƙwalwa a lokaci ɗaya - babu ɗayansu da ya dace a cikin RAM ɗin al'ada na ESP32.
Takardun mai ƙera: Shafin wiki na Makerfabs.
Muhimman ƙayyadaddun bayanai
Processor: ESP32-S3, dual core 240 MHz, WiFi 2.4 GHz + Bluetooth 5.0
Ƙwaƙwalwa: 16 MB flash, 8 MB PSRAM (wanda ake buƙata a kusan kowane aiki a nan)
Nuni: 2.8" IPS, 320×240, direban ST7789V, SPI
Taɓawa: GT911 capacitive, yana bin diddigin yatsu 5 a lokaci ɗaya
Kyamara: OV3660, megapixel 3, har zuwa 2048×1536
Makirifo: INMP441 I2S dijital mics biyu (ainihin nau'in sitiriyo)
Magana: MAX98357A class-D amplifier, 3.2 W a cikin 4 Ω
Adana: ramin katin microSD (yanayin SPI)
Wutar: USB-C, haɗin baturi JST, caja TP4056, maɓallin wuta
Hakanan akan allo: WS2812B RGB LED, agogon ainihin lokaci PCF8563T mai baturi, da ma'aunin man baturi MAX17048 wanda ba a jera shi a cikin ƙayyadaddun bayanai na hukuma ba
Fuskokin USB-C guda biyu ba iri ɗaya ba ne. Magana ɗin allo yana raba siginonin sa (IO19 da IO20) tare da tashar USB ta asali, saboda waɗannan pins sune layukan bayanan USB da aka haɗa da hannu na ESP32-S3. Koyaushe ɗora wuta da wutar ta tashar USB-C CH340K (wanda ke gefen maɓallin RESET), kuma saita USB CDC On Boot zuwa Disabled. Yi amfani da tashar da ba daidai ba kuma sauti zai yi kuskure ko ɗorawa zai gaza.
Saitunan Arduino IDE
Waɗannan saitunan suna da muhimmanci. Yawancin matsalolin da mutane ke ba da rahoto game da wannan allo ɗaya ne daga cikin waɗannan ba daidai ba, kuma suna sake saita lokacin da ka canza sigar core, don haka sake duba su bayan kowane canji.
Saiti | Ƙima |
|---|---|
Allo | ESP32S3 Dev Module |
Sigar core ESP32 | 2.0.17 |
PSRAM | OPI PSRAM |
Girman Flash | 16MB (128Mb) |
Tsarin Rarraba | 16M Flash (3MB APP/9.9MB FATFS) |
USB CDC On Boot | Disabled |
Saurin ɗorawa | 921600 |
Goge Duk Flash Kafin ɗorawa | Disabled |
Tashar | tashar USB-C CH340K |
Yi amfani da ESP32 core 2.0.17, ba 3.x ba. Espressif ya cire samfuran gano fuska a cikin na'urar a core 3, don haka ayyukan fuska ba za su iya haɗuwa a can ba. Yin amfani da 2.0.17 yana kiyaye kowane aiki a wannan shafin yana aiki tare da tsari ɗaya. A cikin Boards Manager, zaɓin sigar yana ba ka damar canzawa baya da gaba duk lokacin da kake so.
Yi amfani da GFX Library for Arduino sigar 1.5.6, ba 1.6.x ba. Sakin 1.6 an gina su don ESP32 core 3 kuma suna iya tsayawa a farawa akan core 2.0.17. Idan allonka ya kasance baƙi bayan ɗora, wannan shine abu na farko da za ka duba.
Libraries da ake buƙata
Shigar da waɗannan ta hanyar Tools → Manage Libraries a cikin Arduino IDE. Lambobin sigar suna da muhimmanci - don Allah yi amfani da waɗanda aka jera.
Library | Sigar | Marubuci |
|---|---|---|
GFX Library for Arduino | 1.5.6 | moononournation |
bb_captouch | 1.3.1 | Larry Bank |
Gyara matsala
Alamar | Dalili da gyara |
|---|---|
Allon ya kasance baƙi | Ba daidai ba GFX library sigar (yi amfani da 1.5.6) ko ba daidai ba saitunan allo. |
|
|
Babu abin da ke ɗora / babu tashar COM | Ba daidai ba tashar USB-C, ko direban CH340 bai shigar ba. |
Kamara ta kasa kuma ba ta sake dawowa ba | Layin sake saitin kamara yana da alaƙa da maɓallin RESET na allo, don haka software ba zai iya sake kunna shi ba. Danna RESET. Idan har yanzu ya kasa, sake shigar da kebul na kamara. |
Sauke lambar
Cikakken zanen Arduino don wannan aikin, tare da pins.h da duk abin da yake buƙata, kyauta ne don saukewa.
Bude shi, buɗe fayil ɗin .ino a cikin Arduino IDE, duba saitunan da ke sama, kuma ɗora ta tashar CH340K USB-C.
Inda za ka sayi wannan allo
Waɗannan hanyoyin haɗin gwiwa ne. Ba su kashe maka komai ba kuma suna taimakawa tallafawa lambar kyauta da darussan a wannan shafin - na gode.
Wannan koyawa wani ɓangare ne na: Makerfabs MaTouch AI ESP32S3 2.8" Camera
- Makerfabs MaTouch ESP32-S3 2.8" kyamara Aiwatar da Fuska a Kusa da ESP32-S3 - Babu Intanet, Babu Makullin API
- Makerfabs MaTouch ESP32-S3 2.8" kamara Gina AI Voice Assistant akan ESP32-S3 (Azure + DeepSeek)
- Makerfabs MaTouch ESP32-S3 2.8" kamara ESP32-S3 AI hangar: Nuna da kamara ka tambayi abin da take gani
/*
* ===========================================================================
* 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(); }
}
Abin da zaka iya bukata
-
SauranProduct page for MaTouch AI ESP32S3 2.8" TFT ST7789Vmakerfabs.com
Albarkatun da kuma shafukan da za a duba
-
TakarduMakerfabs MaTouch ESP32-S3 2.8" Camera and Touchscreen: User's Manualwiki.makerfabs.com
-
Takardu
-
TakarduProduct page for MaTouch AI ESP32S3 2.8" TFT ST7789Vmakerfabs.com
-
ZazzageArduino GFX Library on GitHubgithub.com
Fayiloli📁
Fayil da ake buƙata (.h)
Sauran Fayiloli
Tsarin zane
-
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