Hi @KevinKL , Sir actually I am facing an trouble while doing the offline attendance monitor system
error:[VID Wrn]
CH 0 MMF ENC Queue full
error: vipnn not applied
which ever code I do upload I am facing error
/*******************************************************
* OFFLINE ATTENDANCE SYSTEM v16.15 - VIPNN FIXED
* AMB824 Pro2 + ILI9341 TFT (240x320)
*
*
VIPNN FIXED — 3-channel init (CH0 H264 activates VOE)
*
ALL FLICKER FIXED — single drawBitmap() per frame
*
30FPS SMOOTH VIDEO (33ms timing)
*
LED BLINK: GREEN(Registered) + BLUE(Unknown) 500ms
*
Everything else identical to v16.14
*******************************************************/
#include “VideoStream.h”
#include “SPI.h”
#include “AmebaILI9341.h”
#include “TJpg_Decoder.h”
#include “AmebaFatFS.h”
#include “NNFaceDetectionRecognition.h”
#include “StreamIO.h”
#include
// ─── Pin & Screen ─────────────────────────────────────
#define TFT_RESET 5
#define TFT_DC 4
#define TFT_CS SPI_SS
#define TFT_WIDTH 240
#define TFT_HEIGHT 320
// ─── Layout ───────────────────────────────────────────
#define HEADER_H 28
#define FOOTER_H 38
#define VIDEO_Y HEADER_H
#define VIDEO_H (TFT_HEIGHT - HEADER_H - FOOTER_H)
// ─── BOX ──────────────────────────────────────────────
#define BOX_W 220
#define BOX_H (VIDEO_H - 6)
#define BOX_X ((TFT_WIDTH - BOX_W) / 2)
#define BOX_Y (VIDEO_Y + 3)
#define BOX_T 10
// ─── Hardware ─────────────────────────────────────────
#define BUTTON_PIN 16
#define BLUE_LED LED_B
#define GREEN_LED LED_G
// ── CHANNELS — 3-channel fix for vipnn ──
#define CH_H264 0 // H264 — activates VOE engine (MUST be CH0)
#define CH_JPEG 1 // JPEG — TFT display
#define CH_NN 3 // NN face recognition
#define NN_W 576
#define NN_H 320
// ─── Files ────────────────────────────────────────────
#define USER_DIR “/users”
#define REG_FILE “/users/NEW_FILE.txt”
#define ATT_FILE “/attendance.csv”
// ─── Timing ───────────────────────────────────────────
#define VIDEO_FPS_MS 33
#define LED_MS 500
// ─── Colors ───────────────────────────────────────────
#define C_BLACK 0x0000
#define C_WHITE 0xFFFF
#define C_RED 0xF800
#define C_GREEN 0x07E0
#define C_BLUE 0x001F
#define C_YELLOW 0xFFE0
#define C_ORANGE 0xFD20
#define C_DGRAY 0x7BEF
#define C_NAVY 0x000F
// ─── Objects ──────────────────────────────────────────
VideoSetting cfgH264(VIDEO_FHD, 30, VIDEO_H264, 0); // CH0 — VOE activator
VideoSetting cfgJPEG(VIDEO_VGA, 30, VIDEO_JPEG, 1); // CH1 — display
VideoSetting cfgNN(NN_W, NN_H, 15, VIDEO_RGB, 0); // CH3 — NN
CameraSetting cfgCam;
AmebaILI9341 tft(TFT_CS, TFT_DC, TFT_RESET);
NNFaceDetectionRecognition facerecog;
StreamIO streamerNN(1, 1); // CH3 → facerecog
AmebaFatFS fs;
// ─── Framebuffer ──────────────────────────────────────
uint16_t *frameBuf = nullptr;
// ─── State ────────────────────────────────────────────
bool regMode = false;
bool lastBtn = HIGH;
unsigned long lastPress = 0;
int pressCount = 0;
String statusMsg = “”;
unsigned long statusTime = 0;
bool faceIn = false;
bool faceReg = false;
String faceName = “”;
String lastAttName = “”;
unsigned long lastFaceTime = 0;
uint32_t imgAddr = 0;
uint32_t imgLen = 0;
bool ledState = false;
unsigned long lastLed = 0;
bool isReg = false;
unsigned long frameCount = 0;
int currentFPS = 0;
uint16_t gBoxColor = C_WHITE;
// ─── Framebuf helpers ─────────────────────────────────
void fbFill(int x, int y, int w, int h, uint16_t c) {
int x0=x<0?0:x, x1=x+w>TFT_WIDTH ?TFT_WIDTH :x+w;
int y0=y<0?0:y, y1=y+h>TFT_HEIGHT?TFT_HEIGHT:y+h;
for (int r=y0; r<y1; r++)
for (int col=x0; col<x1; col++)
frameBuf[r*TFT_WIDTH+col]=c;
}
void fbHLine(int x, int y, int w, uint16_t c) {
if (y<0||y>=TFT_HEIGHT) return;
int x1=x+w>TFT_WIDTH?TFT_WIDTH:x+w;
for (int col=x<0?0:x; col<x1; col++)
frameBuf[y*TFT_WIDTH+col]=c;
}
void fbVLine(int x, int y, int h, uint16_t c) {
if (x<0||x>=TFT_WIDTH) return;
int y1=y+h>TFT_HEIGHT?TFT_HEIGHT:y+h;
for (int row=y<0?0:y; row<y1; row++)
frameBuf[row*TFT_WIDTH+x]=c;
}
// ─── 5x7 pixel font ───────────────────────────────────
static const uint8_t font5x7[][5] = {
{0x00,0x00,0x00,0x00,0x00},{0x00,0x00,0x5F,0x00,0x00},
{0x00,0x07,0x00,0x07,0x00},{0x14,0x7F,0x14,0x7F,0x14},
{0x24,0x2A,0x7F,0x2A,0x12},{0x23,0x13,0x08,0x64,0x62},
{0x36,0x49,0x55,0x22,0x50},{0x00,0x05,0x03,0x00,0x00},
{0x00,0x1C,0x22,0x41,0x00},{0x00,0x41,0x22,0x1C,0x00},
{0x08,0x2A,0x1C,0x2A,0x08},{0x08,0x08,0x3E,0x08,0x08},
{0x00,0x50,0x30,0x00,0x00},{0x08,0x08,0x08,0x08,0x08},
{0x00,0x60,0x60,0x00,0x00},{0x20,0x10,0x08,0x04,0x02},
{0x3E,0x51,0x49,0x45,0x3E},{0x00,0x42,0x7F,0x40,0x00},
{0x42,0x61,0x51,0x49,0x46},{0x21,0x41,0x45,0x4B,0x31},
{0x18,0x14,0x12,0x7F,0x10},{0x27,0x45,0x45,0x45,0x39},
{0x3C,0x4A,0x49,0x49,0x30},{0x01,0x71,0x09,0x05,0x03},
{0x36,0x49,0x49,0x49,0x36},{0x06,0x49,0x49,0x29,0x1E},
{0x00,0x36,0x36,0x00,0x00},{0x00,0x56,0x36,0x00,0x00},
{0x00,0x08,0x14,0x22,0x41},{0x14,0x14,0x14,0x14,0x14},
{0x41,0x22,0x14,0x08,0x00},{0x02,0x01,0x51,0x09,0x06},
{0x32,0x49,0x79,0x41,0x3E},{0x7E,0x11,0x11,0x11,0x7E},
{0x7F,0x49,0x49,0x49,0x36},{0x3E,0x41,0x41,0x41,0x22},
{0x7F,0x41,0x41,0x22,0x1C},{0x7F,0x49,0x49,0x49,0x41},
{0x7F,0x09,0x09,0x01,0x01},{0x3E,0x41,0x41,0x49,0x7A},
{0x7F,0x08,0x08,0x08,0x7F},{0x00,0x41,0x7F,0x41,0x00},
{0x20,0x40,0x41,0x3F,0x01},{0x7F,0x08,0x14,0x22,0x41},
{0x7F,0x40,0x40,0x40,0x40},{0x7F,0x02,0x04,0x02,0x7F},
{0x7F,0x04,0x08,0x10,0x7F},{0x3E,0x41,0x41,0x41,0x3E},
{0x7F,0x09,0x09,0x09,0x06},{0x3E,0x41,0x51,0x21,0x5E},
{0x7F,0x09,0x19,0x29,0x46},{0x46,0x49,0x49,0x49,0x31},
{0x01,0x01,0x7F,0x01,0x01},{0x3F,0x40,0x40,0x40,0x3F},
{0x1F,0x20,0x40,0x20,0x1F},{0x7F,0x20,0x18,0x20,0x7F},
{0x63,0x14,0x08,0x14,0x63},{0x03,0x04,0x78,0x04,0x03},
{0x61,0x51,0x49,0x45,0x43},{0x00,0x00,0x7F,0x41,0x41},
{0x02,0x04,0x08,0x10,0x20},{0x41,0x41,0x7F,0x00,0x00},
{0x04,0x02,0x01,0x02,0x04},{0x40,0x40,0x40,0x40,0x40},
{0x00,0x01,0x02,0x04,0x00},{0x20,0x54,0x54,0x54,0x78},
{0x7F,0x48,0x44,0x44,0x38},{0x38,0x44,0x44,0x44,0x20},
{0x38,0x44,0x44,0x48,0x7F},{0x38,0x54,0x54,0x54,0x18},
{0x08,0x7E,0x09,0x01,0x02},{0x08,0x14,0x54,0x54,0x3C},
{0x7F,0x08,0x04,0x04,0x78},{0x00,0x44,0x7D,0x40,0x00},
{0x20,0x40,0x44,0x3D,0x00},{0x00,0x7F,0x10,0x28,0x44},
{0x00,0x41,0x7F,0x40,0x00},{0x7C,0x04,0x18,0x04,0x78},
{0x7C,0x08,0x04,0x04,0x78},{0x38,0x44,0x44,0x44,0x38},
{0x7C,0x14,0x14,0x14,0x08},{0x08,0x14,0x14,0x18,0x7C},
{0x7C,0x08,0x04,0x04,0x08},{0x48,0x54,0x54,0x54,0x20},
{0x04,0x3F,0x44,0x40,0x20},{0x3C,0x40,0x40,0x40,0x7C},
{0x1C,0x20,0x40,0x20,0x1C},{0x3C,0x40,0x30,0x40,0x3C},
{0x44,0x28,0x10,0x28,0x44},{0x0C,0x50,0x50,0x50,0x3C},
{0x44,0x64,0x54,0x4C,0x44},{0x00,0x08,0x36,0x41,0x00},
{0x00,0x00,0x7F,0x00,0x00},{0x00,0x41,0x36,0x08,0x00},
{0x08,0x08,0x2A,0x1C,0x08},
};
void fbChar(int x, int y, char ch, uint16_t c, int s=1) {
if (ch<32||ch>126) ch=32;
const uint8_t *bm=font5x7[ch-32];
for (int col=0;col<5;col++) {
uint8_t bits=bm[col];
for (int row=0;row<7;row++) {
if (bits&(1<<row)) {
for (int sy=0;sy<s;sy++)
for (int sx=0;sx<s;sx++) {
int px=x+col*s+sx,py=y+row*s+sy;
if (px>=0&&px<TFT_WIDTH&&py>=0&&py<TFT_HEIGHT)
frameBuf[py*TFT_WIDTH+px]=c;
}
}
}
}
}
void fbText(int x, int y, const char *str, uint16_t c, int s=1) {
int cx=x;
while(*str){fbChar(cx,y,*str++,c,s);cx+=(5+1)*s;}
}
int fbTextW(const char *str, int s=1){
return strlen(str)*(5+1)*s;
}
// ─── TJpgDec callback — into frameBuf only ────────────
bool tft_output(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t* bmp) {
if (!frameBuf) return false;
for (int row=0; row<h; row++) {
int dy=y+row;
if (dy<VIDEO_Y||dy>=VIDEO_Y+VIDEO_H) continue;
for (int col=0; col<w; col++) {
int dx=x+col;
if (dx<0||dx>=TFT_WIDTH) continue;
frameBuf[dy*TFT_WIDTH+dx]=bmp[row*w+col];
}
}
return true;
}
// ─── Box border into frameBuf ─────────────────────────
void fbDrawBox(uint16_t c) {
fbFill(BOX_X, BOX_Y, BOX_W, BOX_T, c);
fbFill(BOX_X, BOX_Y+BOX_H-BOX_T, BOX_W, BOX_T, c);
fbFill(BOX_X, BOX_Y+BOX_T, BOX_T, BOX_H-2*BOX_T, c);
fbFill(BOX_X+BOX_W-BOX_T,BOX_Y+BOX_T, BOX_T, BOX_H-2*BOX_T, c);
}
// ─── LED blink — identical to v16.14 ──────────────────
void updateLEDs() {
if (millis()-lastLed<LED_MS) return;
lastLed=millis(); ledState=!ledState;
if (faceReg) {
digitalWrite(GREEN_LED,ledState); digitalWrite(BLUE_LED,LOW);
} else if (faceIn) {
digitalWrite(BLUE_LED,ledState); digitalWrite(GREEN_LED,LOW);
} else {
digitalWrite(BLUE_LED,LOW); digitalWrite(GREEN_LED,LOW);
}
}
String getTime() {
unsigned long s=millis()/1000; char b[9];
snprintf(b,9,“%02lu:%02lu:%02lu”,s/3600%24,s/60%60,s%60);
return String(b);
}
String getDate() {
char b[12];
snprintf(b,12,“2026-02-%02d”,(int)(14+(millis()/86400000UL)%28));
return String(b);
}
void statusShow(String m){statusMsg=m;statusTime=millis();}
// ─── SD — identical to v16.14 ─────────────────────────
void initSD() {
fs.begin();
if (!fs.exists(USER_DIR)) fs.mkdir(USER_DIR);
if (!fs.exists(REG_FILE)) {
File f=fs.open(REG_FILE);
if(f){f.println(“# REGISTERED”);f.close();}
}
if (!fs.exists(ATT_FILE)) {
File f=fs.open(ATT_FILE);
if(f){f.println(“Name,Date,Time”);f.close();}
}
fs.end();
}
bool nameRegistered(String name) {
if(name==“unknown”||name.length()==0) return false;
fs.begin(); File f=fs.open(REG_FILE); bool found=false;
if(f){while(f.available()){String l=f.readStringUntil(‘\n’);l.trim();if(l==name){found=true;break;}}f.close();}
fs.end(); return found;
}
void saveName(String name) {
if(nameRegistered(name)) return;
fs.begin(); File f=fs.open(REG_FILE);
if(f){f.seek(f.size());f.println(name);f.close();}
fs.end();
}
void markAttendance(String name) {
String date=getDate(),time=getTime();
fs.begin(); File rf=fs.open(ATT_FILE); bool already=false;
if(rf){while(rf.available()){String l=rf.readStringUntil(‘\n’);if(l.indexOf(name+“,”+date)>=0){already=true;break;}}rf.close();}
if(!already){File wf=fs.open(ATT_FILE);if(wf){wf.seek(wf.size());wf.println(name+“,”+date+“,”+time);wf.close();}statusShow("Att: "+name);}
fs.end();
}
// ─── Face callback — identical to v16.14 ──────────────
void FRCallback(std::vector results) {
if(isReg) return;
if(results.empty()||facerecog.getResultCount()==0){
faceIn=false;faceReg=false;faceName=“”;gBoxColor=C_WHITE;return;
}
String name=String(results[0].name());
if(name.length()==0||name==" "){
faceIn=false;faceReg=false;faceName=“”;gBoxColor=C_WHITE;return;
}
faceIn=true;faceName=name;lastFaceTime=millis();
if(name==“unknown”){
faceReg=false;gBoxColor=C_RED;
} else {
faceReg=nameRegistered(name);
gBoxColor=faceReg?C_GREEN:C_RED;
if(faceReg&&name!=lastAttName){markAttendance(name);lastAttName=name;}
}
}
// ─── Button — identical to v16.14 ─────────────────────
void handleButton() {
bool btn=digitalRead(BUTTON_PIN);
if(btn==LOW&&lastBtn==HIGH){
delay(50); unsigned long now=millis();
if(now-lastPress<500){if(++pressCount>=2){regMode=false;statusShow(“REG OFF”);pressCount=0;}}
else{pressCount=1;}
lastPress=now;
if(pressCount==1&&!regMode){regMode=true;statusShow(“REG ON”);}
}
lastBtn=btn;
}
void updateFPS() {
static unsigned long lastFPSTime=0;
if(millis()-lastFPSTime>=1000){currentFPS=frameCount;frameCount=0;lastFPSTime=millis();}
}
// ─── Serial — identical to v16.14 ─────────────────────
void handleSerial() {
if(!Serial.available()) return;
String cmd=Serial.readStringUntil(‘\n’); cmd.trim();
if(cmd.startsWith(“REG=”)&®Mode){
String name=cmd.substring(4); name.trim();
if(name.length()==0) return;
isReg=true;
tft.fillRectangle(0,0,TFT_WIDTH,TFT_HEIGHT,C_BLUE);
tft.setForeground(C_WHITE);tft.setFontSize(2);
tft.setCursor(20,120);tft.print(“REGISTERING”);
tft.setFontSize(1);tft.setCursor(70,150);tft.print(name);
facerecog.registerFace(name.c_str());delay(800);
saveName(name);
facerecog.backupRegisteredFace();delay(500);
tft.fillRectangle(0,90,TFT_WIDTH,130,C_GREEN);
tft.setForeground(C_BLACK);tft.setFontSize(3);
tft.setCursor(30,130);tft.print(“SUCCESS!”);
delay(1200);
statusShow("Saved: "+name);
regMode=false;isReg=false;
memset(frameBuf,0,TFT_WIDTH*TFT_HEIGHT*sizeof(uint16_t));
Serial.println("Registered: "+name);
}
}
// ─── Render — identical to v16.14 ─────────────────────
void renderFrame() {
if(isReg) return;
frameCount++;
Camera.getImage(CH_JPEG,&imgAddr,&imgLen);
if(imgAddr&&imgLen>0)
TJpgDec.drawJpg(0,VIDEO_Y,(uint8_t*)imgAddr,imgLen);
fbDrawBox(gBoxColor);
fbFill(0,0,TFT_WIDTH,HEADER_H,C_NAVY);
fbHLine(0,HEADER_H-1,TFT_WIDTH,C_BLUE);
if(regMode){
fbText(4,6,“** REG MODE **”,C_YELLOW,1);
fbText(4,18,“Serial: REG=Name”,C_ORANGE,1);
} else {
fbText(4,6,“v16.15 ATTENDANCE”,C_WHITE,1);
char fps[12]; snprintf(fps,12,“FPS:%d”,currentFPS);
fbText(4,18,fps,C_DGRAY,1);
}
String t=getTime(); int tw=fbTextW(t.c_str(),1);
fbText(TFT_WIDTH-tw-4,11,t.c_str(),C_YELLOW,1);
uint16_t footerBg=regMode?C_ORANGE:C_NAVY;
fbFill(0,TFT_HEIGHT-FOOTER_H,TFT_WIDTH,FOOTER_H,footerBg);
fbHLine(0,TFT_HEIGHT-FOOTER_H,TFT_WIDTH,C_BLUE);
String footerTxt; uint16_t footerCol;
if(regMode){footerTxt=“REG MODE”;footerCol=C_BLACK;}
else if(!faceIn){footerTxt=“NO FACE”;footerCol=C_DGRAY;}
else if(faceReg){footerTxt=faceName;footerCol=C_GREEN;}
else{footerTxt=“UNKNOWN”;footerCol=C_RED;}
if(footerTxt.length()>20) footerTxt=footerTxt.substring(0,17)+“…”;
int fw=fbTextW(footerTxt.c_str(),2);
int fx=(TFT_WIDTH-fw)/2; if(fx<4)fx=4;
int fy=TFT_HEIGHT-FOOTER_H+(FOOTER_H-14)/2;
fbText(fx,fy,footerTxt.c_str(),footerCol,2);
const char *lbl=!faceIn?“NO FACE”:faceReg?“REGISTERED”:“UNKNOWN”;
int lw=fbTextW(lbl,1),lx=BOX_X+(BOX_W-lw)/2,ly=BOX_Y+(BOX_H-7)/2;
fbFill(lx-6,ly-4,lw+12,15,C_BLACK);
fbText(lx,ly,lbl,gBoxColor,1);
if(statusMsg!=“”&&millis()-statusTime<=3000){
int sw=fbTextW(statusMsg.c_str(),1)+20;
int sx=std::max(5,(TFT_WIDTH-sw)/2),sy=TFT_HEIGHT/2-10;
fbFill(sx,sy,sw,20,C_BLACK);
fbHLine(sx,sy,sw,C_YELLOW);fbHLine(sx,sy+19,sw,C_YELLOW);
fbVLine(sx,sy,20,C_YELLOW);fbVLine(sx+sw-1,sy,20,C_YELLOW);
fbText(sx+8,sy+6,statusMsg.c_str(),C_YELLOW,1);
}
tft.drawBitmap(0,0,TFT_WIDTH,TFT_HEIGHT,frameBuf);
}
// ─────────────────────────────────────────────────────
void setup() {
Serial.begin(115200);
delay(1500);
Serial.println(“v16.15 - VIPNN FIXED + NO FLICKER”);
pinMode(BLUE_LED,OUTPUT); pinMode(GREEN_LED,OUTPUT);
pinMode(BUTTON_PIN,INPUT_PULLUP);
frameBuf=(uint16_t*)malloc(TFT_WIDTH*TFT_HEIGHT*sizeof(uint16_t));
if(!frameBuf){Serial.println(“FATAL: malloc!”);while(1)delay(1000);}
memset(frameBuf,0,TFT_WIDTH*TFT_HEIGHT*sizeof(uint16_t));
SPI.setDefaultFrequency(40000000);
tft.begin(); tft.setRotation(0); tft.clr();
TJpgDec.setJpgScale(2);
TJpgDec.setCallback(tft_output);
initSD();
/* ══════════════════════════════════════════
3-CHANNEL INIT — fixes vipnn not applied
CH0 H264 must be configured first to
activate the VOE/VIPNN hardware engine
══════════════════════════════════════════ */
Camera.configVideoChannel(CH_H264, cfgH264); // CH0 first — activates VOE
Camera.configVideoChannel(CH_JPEG, cfgJPEG); // CH1 — display
Camera.configVideoChannel(CH_NN, cfgNN); // CH3 — NN
Camera.videoInit();
// Start CH0 H264 — must begin to keep VOE active
// No StreamIO needed — just channelBegin keeps VOE alive
Camera.channelBegin(CH_H264);
facerecog.configVideo(cfgNN);
facerecog.modelSelect(FACE_RECOGNITION,NA_MODEL,
DEFAULT_SCRFD,DEFAULT_MOBILEFACENET);
facerecog.begin();
facerecog.setResultCallback(FRCallback);
streamerNN.registerInput(Camera.getStream(CH_NN));
streamerNN.registerOutput(facerecog);
streamerNN.begin();
Camera.channelBegin(CH_JPEG);
Camera.channelBegin(CH_NN);
facerecog.restoreRegisteredFace();
tft.fillRectangle(0,0,TFT_WIDTH,TFT_HEIGHT,C_GREEN);
tft.setForeground(C_BLACK);tft.setFontSize(3);
tft.setCursor(40,140);tft.print(“READY!”);
delay(1200);
memset(frameBuf,0,TFT_WIDTH*TFT_HEIGHT*sizeof(uint16_t));
Serial.println(“v16.15 running!”);
}
void loop() {
if(!isReg&&faceIn&&millis()-lastFaceTime>1500){
faceIn=false;faceReg=false;faceName=“”;gBoxColor=C_WHITE;
}
updateLEDs();
updateFPS();
handleButton();
handleSerial();
static unsigned long lastFrame=0;
if(millis()-lastFrame>=VIDEO_FPS_MS){
lastFrame=millis();
renderFrame();
}
}
here is the code where i am facing error
#include “VideoStream.h”
#include “SPI.h”
#include “AmebaILI9341.h”
#include <JPEGDEC_Libraries/JPEGDEC.h>
#include “NNFaceDetectionRecognition.h”
#include “StreamIO.h”
#include “AmebaFatFS.h”
/* ===== CHANNELS ===== */
#define CHANNEL_JPEG 0
#define CHANNEL_NN 3
/* ===== TFT PINS ===== */
#define TFT_RESET 5
#define TFT_DC 4
#define TFT_CS SPI_SS
/* ===== GPIO ===== */
#define REG_BUTTON 16
#define BLUE_LED LED_B
#define GREEN_LED LED_G
/* ===== DISPLAY — PORTRAIT 240x320 ===== */
#define ILI9341_SPI_FREQUENCY 40000000
#define TFT_W 240
#define TFT_H 320
/* ===== JPEG SOURCE SIZE (after SCALE_HALF of VGA 640x480) ===== */
#define CAM_W 320 // landscape width after half scale
#define CAM_H 240 // landscape height after half scale
/* ===== LAYOUT (portrait) ===== */
#define HEADER_H 20
#define STATUS_H 20
#define VIDEO_Y HEADER_H // 20
#define VIDEO_H (TFT_H - HEADER_H - STATUS_H) // 280
#define STATUS_Y (TFT_H - STATUS_H) // 300
/* ===== NO-FACE TIMEOUT ===== */
#define NO_FACE_MS 300
/* ===== COLORS RGB565 ===== */
#define C_BLACK 0x0000
#define C_WHITE 0xFFFF
#define C_RED 0xF800
#define C_GREEN 0x07E0
#define C_BLUE 0x001F
#define C_CYAN 0x07FF
#define C_YELLOW 0xFFE0
#define C_ORANGE 0xFD20
#define C_DGRAY 0x7BEF
#define C_NAVY 0x000F
/* ===== SD ===== */
#define ATT_FILE “/attendance.csv”
/* ===== VIDEO CONFIGS ===== */
VideoSetting configJPEG(VIDEO_VGA, 30, VIDEO_JPEG, 1);
VideoSetting configNN(576, 320, 10, VIDEO_RGB, 0);
/* ===== OBJECTS ===== */
AmebaILI9341 tft(TFT_CS, TFT_DC, TFT_RESET);
JPEGDEC jpeg;
NNFaceDetectionRecognition facerecog;
StreamIO streamNN(1, 1);
AmebaFatFS fs;
/* ===== FRAME BUFFER — heap allocated ===== */
uint32_t img_addr = 0;
uint32_t img_len = 0;
uint16_t *frameBuf = nullptr; // TFT_W * TFT_H * 2 = 153600 bytes
/* ===== STATE ===== */
bool regMode = false;
bool lastBtn = HIGH;
/* ===== FACE STATE ===== */
bool faceDetected = false;
unsigned long lastFaceTime = 0;
std::vector lastResults;
/* ===== ATTENDANCE — in-memory duplicate tracking ===== */
#define MAX_MARKED 50
char markedNames[MAX_MARKED][32];
int markedCount = 0;
bool alreadyMarked(const char *name) {
for (int i = 0; i < markedCount; i++)
if (strcmp(markedNames[i], name) == 0) return true;
return false;
}
void addToMarked(const char *name) {
if (markedCount < MAX_MARKED)
strncpy(markedNames[markedCount++], name, 31);
}
/* ===========================
JPEG DRAW — rotate 90° CW inline
Source: landscape CAM_W(320) x CAM_H(240)
Dest: portrait TFT_W(240) x TFT_H(320)
90° CW formula:
dst_col = CAM_H - 1 - src_row → 0..239 = TFT_W
dst_row = src_col → 0..319 = TFT_H
VIDEO area occupies dst_rows VIDEO_Y..STATUS_Y-1 (20..299)
Camera rows 0..CAM_H map to VIDEO_H(280) rows after scaling:
dst_row = VIDEO_Y + src_col * VIDEO_H / CAM_W
=========================== */
int JPEGDraw(JPEGDRAW *pDraw) {
if (!frameBuf) return 0;
for (int y = 0; y < pDraw->iHeight; y++) {
int src_row = pDraw->y + y;
if (src_row < 0 || src_row >= CAM_H) continue;
for (int x = 0; x < pDraw->iWidth; x++) {
int src_col = pDraw->x + x;
if (src_col < 0 || src_col >= CAM_W) continue;
// 90° CW rotation
int dst_col = CAM_H - 1 - src_row; // 0..239
int dst_row = VIDEO_Y + src_col * VIDEO_H / CAM_W; // 20..299
if (dst_col < 0 || dst_col >= TFT_W) continue;
if (dst_row < VIDEO_Y || dst_row >= STATUS_Y) continue;
frameBuf[dst_row * TFT_W + dst_col] =
pDraw->pPixels[y * pDraw->iWidth + x];
}
}
return 1;
}
/* ===========================
FRAMEBUF DRAW HELPERS
=========================== */
void fbFill(int x, int y, int w, int h, uint16_t c) {
int x0 = x < 0 ? 0 : x, x1 = x+w > TFT_W ? TFT_W : x+w;
int y0 = y < 0 ? 0 : y, y1 = y+h > TFT_H ? TFT_H : y+h;
for (int r = y0; r < y1; r++)
for (int col = x0; col < x1; col++)
frameBuf[r * TFT_W + col] = c;
}
void fbHLine(int x, int y, int w, uint16_t c) {
if (y < 0 || y >= TFT_H) return;
int x1 = x+w > TFT_W ? TFT_W : x+w;
for (int col = x < 0 ? 0 : x; col < x1; col++)
frameBuf[y * TFT_W + col] = c;
}
void fbVLine(int x, int y, int h, uint16_t c) {
if (x < 0 || x >= TFT_W) return;
int y1 = y+h > TFT_H ? TFT_H : y+h;
for (int row = y < 0 ? 0 : y; row < y1; row++)
frameBuf[row * TFT_W + x] = c;
}
void fbRect(int x, int y, int w, int h, uint16_t c) {
fbHLine(x, y, w, c);
fbHLine(x, y+h-1, w, c);
fbVLine(x, y, h, c);
fbVLine(x+w-1, y, h, c);
}
void fbThickRect(int x, int y, int w, int h, uint16_t c, int t) {
for (int i = 0; i < t; i++)
fbRect(x+i, y+i, w-2*i, h-2*i, c);
}
/* ===========================
TIME STRING (uptime hh:mm:ss)
=========================== */
String getTimeStr() {
unsigned long s = millis() / 1000;
char buf[9];
sprintf(buf, “%02lu:%02lu:%02lu”, s/3600, (s%3600)/60, s%60);
return String(buf);
}
/* ===========================
MARK ATTENDANCE TO SD
=========================== */
bool markAttendance(String name) {
if (alreadyMarked(name.c_str())) return false; // skip — already saved
String ts = getTimeStr();
File log = fs.open(ATT_FILE, FA_WRITE | FA_OPEN_APPEND);
if (log) {
log.println(name + “,” + ts);
log.close();
addToMarked(name.c_str());
Serial.println("ATT SAVED: " + name + " @ " + ts);
return true;
}
Serial.println("ATT ERROR: SD write failed for " + name);
return false;
}
/* ===========================
FACE CALLBACK
=========================== */
void FRPostProcess(std::vector results) {
lastResults = results;
faceDetected = (results.size() > 0);
if (faceDetected) {
lastFaceTime = millis();
Serial.println("FACE: " + String(results[0].name()));
}
}
/* ===========================
SETUP
=========================== */
void setup() {
Serial.begin(115200);
pinMode(REG_BUTTON, INPUT_PULLUP);
pinMode(BLUE_LED, OUTPUT);
pinMode(GREEN_LED, OUTPUT);
digitalWrite(BLUE_LED, LOW);
digitalWrite(GREEN_LED, LOW);
/* ── HEAP BUFFER ── */
frameBuf = (uint16_t*)malloc(TFT_W * TFT_H * sizeof(uint16_t));
if (!frameBuf) {
Serial.println(“FATAL: frameBuf malloc failed!”);
while (1) delay(1000);
}
memset(frameBuf, 0, TFT_W * TFT_H * sizeof(uint16_t));
/* ── TFT ── */
SPI.setDefaultFrequency(ILI9341_SPI_FREQUENCY);
tft.begin();
tft.setRotation(4); // portrait 240x320
// if upside-down change to: tft.setRotation(2)
/* ── WELCOME SCREEN ── */
tft.fillRectangle(0, 0, TFT_W, TFT_H, C_NAVY);
// header stripe
tft.fillRectangle(0, 0, TFT_W, 40, C_BLUE);
tft.setForeground(C_WHITE);
tft.setFontSize(2);
tft.setCursor(14, 12);
tft.print(“ATTENDANCE”);
tft.fillRectangle(0, 40, TFT_W, 2, C_CYAN);
// info
tft.setForeground(C_CYAN);
tft.setFontSize(1);
tft.setCursor(68, 75);
tft.print(“AMB82-MINI”);
tft.setForeground(C_WHITE);
tft.setCursor(20, 100);
tft.print(“Face Recognition System”);
tft.setCursor(44, 125);
tft.print(“Portrait 240 x 320”);
// divider
tft.fillRectangle(20, 155, TFT_W-40, 1, C_DGRAY);
// hints
tft.setForeground(C_DGRAY);
tft.setCursor(8, 170);
tft.print(“BTN = Toggle REG mode”);
tft.setCursor(8, 188);
tft.print(“REG=Name to register”);
tft.setCursor(8, 206);
tft.print(“DEL=Name to delete”);
tft.setCursor(8, 224);
tft.print(“RESET to clear all”);
// bottom bar
tft.fillRectangle(0, TFT_H-32, TFT_W, 32, C_BLUE);
tft.setForeground(C_YELLOW);
tft.setCursor(52, TFT_H-18);
tft.print(“Initializing…”);
delay(2000);
/* ── SD ── */
fs.begin();
if (!fs.exists(ATT_FILE)) {
File h = fs.open(ATT_FILE, FA_WRITE);
if (h) {
h.println(“Name,Time”);
h.close();
Serial.println(“SD: attendance.csv created”);
} else {
Serial.println(“SD: WARNING — could not create attendance.csv”);
}
} else {
Serial.println(“SD: attendance.csv found”);
}
/* ── CAMERA + NN ── */
Camera.configVideoChannel(CHANNEL_JPEG, configJPEG);
Camera.configVideoChannel(CHANNEL_NN, configNN);
Camera.videoInit();
facerecog.configVideo(configNN);
facerecog.modelSelect(FACE_RECOGNITION, NA_MODEL, DEFAULT_SCRFD, DEFAULT_MOBILEFACENET);
facerecog.begin();
facerecog.restoreRegisteredFace();
facerecog.setResultCallback(FRPostProcess);
streamNN.registerInput(Camera.getStream(CHANNEL_NN));
streamNN.setStackSize();
streamNN.setTaskPriority();
streamNN.registerOutput(facerecog);
streamNN.begin();
Camera.channelBegin(CHANNEL_JPEG);
Camera.channelBegin(CHANNEL_NN);
Serial.println(“READY — BTN=REG | REG=Name | DEL=Name | RESET”);
}
/* ===========================
LOOP
=========================== */
void loop() {
/* ── BUTTON ── */
bool btn = digitalRead(REG_BUTTON);
if (btn == LOW && lastBtn == HIGH) {
regMode = !regMode;
Serial.println(regMode ? “REG MODE ON” : “NORMAL MODE”);
delay(200);
}
lastBtn = btn;
/* ── SERIAL ── */
if (Serial.available()) {
String cmd = Serial.readStringUntil(‘\n’); cmd.trim();
if (cmd.startsWith(“REG=”) && regMode) {
String n = cmd.substring(4); n.trim();
if (n.length()) {
facerecog.registerFace(n.c_str());
facerecog.backupRegisteredFace();
Serial.println("Registered: " + n);
}
} else if (cmd.startsWith(“DEL=”)) {
String n = cmd.substring(4); n.trim();
facerecog.removeFace(n);
facerecog.backupRegisteredFace();
Serial.println("Deleted: " + n);
} else if (cmd == “RESET”) {
facerecog.resetRegisteredFace();
facerecog.backupRegisteredFace();
markedCount = 0; // clear in-memory attendance too
Serial.println(“All faces + attendance memory cleared”);
}
}
/* ── FACE TIMEOUT ── */
bool faceNow = faceDetected &&
((millis() - lastFaceTime) < NO_FACE_MS);
/* ── DECODE JPEG → frameBuf (rotated 90°CW by JPEGDraw) ── */
Camera.getImage(CHANNEL_JPEG, &img_addr, &img_len);
if (img_len > 0) {
// clear video area before decode
fbFill(0, VIDEO_Y, TFT_W, VIDEO_H, C_BLACK);
jpeg.openFLASH((uint8_t*)img_addr, img_len, JPEGDraw);
jpeg.decode(0, 0, JPEG_SCALE_HALF); // 640x480 → 320x240 → rotated into portrait
jpeg.close();
}
fbFill(0, 0, TFT_W, HEADER_H, C_NAVY);
fbFill(0, STATUS_Y, TFT_W, STATUS_H, regMode ? C_ORANGE : C_NAVY);
/* ── FACE BOX INTO FRAMEBUF ── */
bool isReg = false;
char nm[64] = “”;
int nameX = 5, nameY = VIDEO_Y + 4;
uint16_t nameCol = C_WHITE;
if (!faceNow) {
// Guide box centered in video area
int bx = (TFT_W - 120) / 2; // 60
int by = VIDEO_Y + (VIDEO_H - 150) / 2; // 85
fbThickRect(bx, by, 120, 150, C_WHITE, 2);
digitalWrite(BLUE_LED, LOW);
digitalWrite(GREEN_LED, LOW);
} else {
FaceRecognitionResult r = lastResults[0];
String name = String(r.name());
isReg = (name != “unknown”);
strncpy(nm, name.c_str(), 63);
digitalWrite(BLUE_LED, !isReg ? HIGH : LOW);
digitalWrite(GREEN_LED, isReg ? HIGH : LOW);
// NN coords are landscape 0.0-1.0
// After 90°CW rotation applied in JPEGDraw:
// portrait_x = (1 - y_norm) * TFT_W
// portrait_y = VIDEO_Y + x_norm * VIDEO_H
int x1 = (int)((1.0f - r.yMax()) * TFT_W);
int y1 = VIDEO_Y + (int)(r.xMin() * VIDEO_H);
int w1 = (int)((r.yMax() - r.yMin()) * TFT_W);
int h1 = (int)((r.xMax() - r.xMin()) * VIDEO_H);
x1 = x1 < 0 ? 0 : (x1 > TFT_W-4 ? TFT_W-4 : x1);
y1 = y1 < VIDEO_Y ? VIDEO_Y : y1;
w1 = w1 < 8 ? 8 : w1;
h1 = h1 < 8 ? 8 : h1;
nameCol = isReg ? C_GREEN : C_RED;
fbThickRect(x1, y1, w1, h1, nameCol, 3);
nameX = x1;
nameY = (y1 > VIDEO_Y + 18) ? y1 - 18 : y1 + h1 + 2;
nameY = nameY < VIDEO_Y ? VIDEO_Y : nameY;
nameY = nameY > STATUS_Y-16 ? STATUS_Y-16 : nameY;
if (isReg) markAttendance(name);
}
/* ── PUSH FULL FRAME TO TFT (single call = no flicker) ── */
tft.drawBitmap(0, 0, TFT_W, TFT_H, frameBuf);
/* ── TEXT OVERLAYS (drawn after bitmap) ── */
// Header
tft.setFontSize(1);
tft.setForeground(C_CYAN);
tft.setCursor(4, 6);
tft.print(regMode ? “** REG MODE **” : “ATTENDANCE”);
tft.setForeground(C_WHITE);
tft.setCursor(TFT_W - 54, 6); // right-aligned time
tft.print(getTimeStr());
// No face label
if (!faceNow) {
tft.setForeground(C_WHITE);
tft.setFontSize(1);
int bx = (TFT_W - 120) / 2;
int by = VIDEO_Y + (VIDEO_H - 150) / 2;
tft.setCursor(bx + 22, by + 68);
tft.print(“NO FACE”);
}
// Face name
if (faceNow && strlen(nm) > 0) {
tft.setForeground(nameCol);
tft.setFontSize(2);
tft.setCursor(nameX, nameY);
tft.print(isReg ? nm : “Unknown”);
}
// Status bar text
tft.setFontSize(1);
tft.setCursor(4, STATUS_Y + 6);
if (regMode) {
tft.setForeground(C_YELLOW);
tft.print(“Serial: REG=YourName”);
} else if (!faceNow) {
tft.setForeground(C_DGRAY);
tft.print(“Waiting for face…”);
} else if (isReg) {
tft.setForeground(C_GREEN);
tft.print("Marked: ");
tft.print(nm);
} else {
tft.setForeground(C_RED);
tft.print(“Unknown — not registered”);
}
}
This is an another code where I am facing issue with display rotation if I change to the landscape it will display portrait and vice versa
Please give me solution
Thank you