Search Code

Makerfabs MaTouch ESP32-S3 2.8" Camera mpe Touchscreen: Bafɔ́ti 3MP na carte SD

Makerfabs MaTouch ESP32-S3 2.8" Camera mpe Touchscreen: Bafɔ́ti 3MP na carte SD

Talisi ya bomoi, bilili ya 3 megapixels, ebombami na SD na ngonga ya solo

Application ya kamera oyo ezali kosala: talisi ya bomoi na pene ya 25 ba images na seconde, ba boutons misato ya kosimba, mpe ba photos ebombami na carte microSD na ngonga mpe mokolo ya solo oyo ewuti na horloge ya board oyo ezali na batterie.

Camera and Touchscreen running on the MaTouch AI ESP32-S3 board Kamera mpe Ecran ya kosimba ezali kosala na board MaTouch AI ESP32-S3

Ba boutons misato

  • SNAP - foto ya mbala moko na résolution ya talisi ya bomoi. Mbangu mpe esalaka tango nyonso.

  • 3 MP - foto ya solo ya 2048×1536. Ecran ekomaka molili pene na secondes mibale; wana ezali driver ya kamera ezali kopema, mpe esili kolimbolama na nse.

  • FLIP - ezali kobalola image tango okotisi lentille mosika na yo moko.

Mpo na nini ecran ekomaka molili mpo na foto ya 3 MP

Format ya pixel ya kamera ezali ya kokangama tango driver ebandi. Okoki kobongola résolution na tango yango, kasi okoki te kobongola wuta RGB565 (oyo ecran esengeli) na JPEG (oyo fisyé esengeli) soki okangi driver te. Boye, nzela ya résolution ya likolo ebengaka esp_camera_deinit(), ebandi lisusu na mode JPEG na résolution ya kokoka, ebengaka foto, na sima ezongisaka talisi ya bomoi. Kopema yango ezali ya solo, ezali bug te.

Ba timestamps na internet te

Ba photos ebombami lokola /IMG_260730_143052.jpg - mobu, sánzá, mokolo, ngonga, minute, seconde. Wana ewuti na horloge ya tango ya solo PCF8563T, oyo ezali kotambola na batterie, boye ngonga eumelaka ata soki babongoli na electricité. Soki horloge ebungisaki nguya, sketch ezali kopelisa yango wuta na ngonga oyo sketch esalelamaki.

Libongoli na ndakisa ya motongi

Ndakisa ya kamera ya motongi moko moko ezali kosala que carte SD ezali na ba pins 2, 42 mpe 41. Ba numéro wana ya ba pins bakopi wuta na board mosusu - na oyo, yango ezali ba pins ya microphone. Carte SD ezali solo na bus SPI ya écran na chip select na yango moko na IO47, oyo code oyo ezali kosalela. Yango ezali na tina soki olingi kamera mpe ba microphones basala na sketch moko.

Kokabola bus SPI moko

Ecran mpe carte SD bazali na ba fils misato ya moko, boye bango mibale basengeli koleka na driver moko ya SPI. Sketch oyo ezali kosalela Arduino_HWSPI na objet ya SPI ya standard mpo na yango. Kosalela driver mosusu mpo na moko na moko ezali ntina ya libunga ya écran oyo ekangamaka mbala moko carte SD ematemi.

Na tina ya board MaTouch AI ESP32-S3 2.8"

Projet nyonso na page oyo ezali kotambola na MaTouch AI ESP32-S3 2.8" TFT ST7789V wuta Makerfabs. Ezali board ya biloko nyonso: écran ya ba couleurs oyo ekoki kosimba, kamera ya 3 megapixels, ba microphones mibale mpe amplificateur ya solo, nyonso ezali kotambola na ESP32-S3 na 8 MB ya PSRAM. Lolenge wana nde esalaka ete ba projets ya AI ekoka kosalema na board moko kaka na eloko mosusu te.

8 MB ya PSRAM ezali na tina koleka motángo mosusu nyonso awa. Ezali yango nde esalaka ete board ekoka kosimba cadre ya kamera, ba secondes moke ya audio oyo ekomami, to foto ya base64 na mémoire na mbala moko - eloko moko te kati na yango ekoki kokota na RAM ya normal ya ESP32.

Bakomá ya motongi: page wiki ya Makerfabs.

Ba caractéristiques ya minene

  • Processeur: ESP32-S3, ba cœurs mibale ya 240 MHz, WiFi 2.4 GHz + Bluetooth 5.0

  • Mémoire: 16 MB flash, 8 MB PSRAM (oyo esengeli na pene ya projet nyonso awa)

  • Ecran: 2.8" IPS, 320×240, driver ST7789V, SPI

  • Kosimba: GT911 capacitif, ezali kolanda ba doigts 5 na mbala moko

  • Kamera: OV3660, 3 megapixels, kino 2048×1536

  • Ba microphones: ba mics mibale ya INMP441 I2S ya digitale (paire ya stereo ya solo)

  • Lisoló: amplificateur MAX98357A classe-D, 3.2 W na 4 Ω

  • Bobombi: slot ya carte microSD (mode SPI)

  • Nguya: USB-C, connecteur ya batterie JST, chargeur TP4056, interrupteur ya nguya

  • Mpe na board: LED RGB WS2812B, horloge ya tango ya solo PCF8563T oyo ezali na batterie, mpe jauge ya carburant ya batterie MAX17048 oyo ezali te na ba spécifications officielles

Ba ports mibale ya USB-C bazali ndenge moko te. Lisoló ya board ezali kokabola ba signaux na yango (IO19 mpe IO20) na port ya native USB, mpo ete ba pins wana nde ezali ba lignes ya données USB ya ESP32-S3 oyo ezali ya kokangama. Tangó nyonso, upload mpe nguya esengeli kosalema na port CH340K USB-C (oyo ezali pembeni ya bouton RESET), mpe tia USB CDC On Boot na Disabled. Soki osaleli port ya mabe, audio ekobebisama to uploads ekofelaka.

Ba réglages ya Arduino IDE

Ba réglages oyo bazali na tina. Mingi ya ba problèmes oyo bato balobaka na board oyo ezali moko ya ba réglages oyo ezali mabe, mpe bango bazongisamaka tango obongoli version ya core, boye tala lisusu bango na sima ya mbongwana nyonso.

Réglage

Valeur

Board

ESP32S3 Dev Module

Version ya core ESP32

2.0.17

PSRAM

OPI PSRAM

Mbula ya Flash

16MB (128Mb)

Plan ya Partition

16M Flash (3MB APP/9.9MB FATFS)

USB CDC On Boot

Disabled

Mbangu ya Upload

921600

Kolimbola Flash nyonso liboso ya Upload

Disabled

Port

port CH340K USB-C

Salelá ESP32 core 2.0.17, kasi 3.x te. Espressif alongolaki ba modèles ya koyéba bilongi na elongi o core 3, yango wana ba projets ya elongi ekosala te kuna. Kokanga 2.0.17 ekomisaka projet nyonso na lokasa oyo ekomi kosala na configuration moko. O Boards Manager, liste ya ba versions ekopesa yo nzela ya kobongola tango nyonso olingi.

Salelá GFX Library for Arduino version 1.5.6, kasi 1.6.x te. Ba versions 1.6 esalemaki mpo na ESP32 core 3 mpe ekoki kokangama na ebandeli na core 2.0.17. Soki écran na yo etikali moyindo nsima ya kotombola, oyo nde esengeli kotala liboso.

Ba bibliothèques oyo esengeli

Tiya yango na nzela ya Tools → Manage Libraries o Arduino IDE. Ba numéro ya ba versions ezali na tina - salelá ba oyo etángami awa.

Bibliothèque

Version

Mokomi

GFX Library for Arduino

1.5.6

moononournation

bb_captouch

1.3.1

Larry Bank

Kokamba mikakatano

Elembo

Motindo mpe libongisi

Écran etikali moyindo

Version ya bibliothèque GFX ezali mabe (salelá 1.5.6) to ba paramètres ya carte ezali mabe.

PSRAM alloc failed to libunga ya caméra 0xffffffff

Tools → PSRAM ezali te na OPI PSRAM.

Eloko moko te etombolami / port COM ezali te

Port USB-C ezali mabe, to driver ya CH340 etiyami te.

Caméra efungwaka mpe ezongaka lisusu te

Ligne ya reset ya caméra esangani na bouton RESET ya carte, yango wana logiciel ekoki kosala yango lisusu te. Finá RESET. Soki efungwaka lisusu, tia lisusu câble ya caméra.

Kokitisa code

Sketch ya Arduino mobimba mpo na projet oyo, elongo na pins.h mpe biloko nyonso mosusu oyo esengeli, ezali ya kokitisa na ofele.

Kokitisa 02_Camera_Touch

Fungola yango, fungola fisyé .ino o Arduino IDE, tala ba paramètres oyo ezali na likoló, mpe tombola na nzela ya port CH340K USB-C.

Esika ya kosomba carte oyo

Ba liens oyo ezali ya affilier. Ezali kotikela yo mbongo te mpe esungaka code ya ofele mpe ba tutoriels na lokasa oyo - matondi.

882-Arduin code for MaTouch AI ESP32S3 2.8in AI Camera: Camera and touch
Langue: C++
/*
 * ===========================================================================
 *  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(); }
}

Fichiers📁

Fichye ki nesesè (.h)

  • secrets.h
    file for Makerfabs MaTouch AI ESP32S3 2.8" TFT Camera module
    secrets.h 0.01 MB

Mokanda mosusu

  • pins.h
    pins file for MaTouch AI ESP32S3 2.8" camera LCD touch screen.
    pins.h 0.01 MB

Schematic

  • MaTouch_AI 2.8“ MaTouch AI ESP32S3 2.8" TFT ST7789V schematic
    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.
    MaTouch_AI 2.8“ SPI TFT ST7789V V1.1.PDF 0.15 MB