一、 所选任务介绍
此次选择的任务是智能门禁与报警系统。
二、 项目介绍
本项目使用ESP32 S3 Sense为主控板,结合DVP摄像头、PDM麦克风、WS2812灯带、LED灯、无源蜂鸣器、ADC按键、OLED屏幕、SG90 360 度伺服电机、超声波传感器实现智能门禁与报警系统。
1.可以通过超声波传感器自动检测是否有人,并自动进入人脸识别检测及声音识别检测流程,过程中使用OLED屏幕显示互动信息,并在不同阶段通过LED灯展示进展,人脸识别及语音识别验证通过则驱动360舵机打开门锁并播放欢迎音乐和灯光。
2.可通过ADC按键在OLED屏幕上查看和删除异常访问记录,并同时启动WEB服务器。在WEB服务器中可以查看、删除异常访问记录的照片、可查看门外实时照片、可调整人脸识别阈值及门禁异常超时限值。
三、 所有使用到的硬件介绍
1. ESP32-S3 Sense
基于 ESP32-S3 芯片,双核、高算力,支持 Wi‑Fi / BLE。自带摄像头接口、麦克风模块。外设有GPIO、PWM、ADC、I2C、SPI、UART 等。在项目中用来作为主控,并通过摄像头及麦克风模块实现人脸识别,通过WIFI实现WEB服务器功能。

2. WS2812 灯带
内置驱动芯片的全彩 LED,单总线控制,只需要一根信号线即可控制每个灯珠的 RGB 颜色,亮度、颜色、动画,供电 5V。在项目中用来显示开门欢迎动画,使用的2号引脚,通过Adafruit的NeoPixel库来控制。

3. 三色 LED 灯
最基础的发光二极管,用来做简单指示。板上自带限流电阻,通过 GPIO 高低电平控制亮灭。在项目中用来指示门禁状态,使用44、43及7号引脚控制。

4. 无源蜂鸣器
没有内置振荡电路,需要 MCU 输出一定频率的方波才能发声。可通过改变频率发出不同音调,做提示音、警报、简易音乐。在项目中用来播放欢迎音乐,用的是9号引脚,驱动直接使用arduino的TONE。

5. ADC 按键
多个按键共用一个 ADC 输入引脚,通过不同分压电阻产生不同电压。节省 GPIO,适合按键多但 IO 紧张的项目。在项目中用来切换默认及简单设置,使用的引脚是1号,在RTOS任务中通过ADC来读取按键的电压值,区分不同的按键输入。

6. OLED 屏幕
0.96寸OLED屏幕,使用SSD1306驱动,具有自发光、对比度高、省电的特点,使用I2C接口。在项目中用来显示交互信息,使用的引脚是5和6。

7. SG90 360 度伺服电机(连续旋转舵机)
360°舵机是连续旋转,PWM不再控角度,而是控舵机的转速和方向。在项目中用来执行开门动作,使用的是8号引脚,通过ESP32Servo库来驱动,当然直接使用LEDC也是可以的。

8. 超声波传感器(CS100A)
通过发射和接收超声波,计算往返时间得到距离。典型量程 2cm–500cm,适合避障、测距、接近检测。在项目中用来监控人员的靠近和离开,使用的引脚是4和3,通过中断来读取数据。

9. 转接底板
转接地板提供了各个传感器模块与主控之间的连接接口,可以方便的进行连接和调换。

最终成品图:

四、 案框图和项目设计思路介绍
1. 使用edgeimpulse训练图像识别模型,实现人脸识别功能。
2. 原计划继续使用edgeimpulse训练语音分类模型来实现语音识别,实际也训练成功了,但是在实际部署的时候发现edgeimpulse没有提供便捷的双模型部署工具和实际案例,操作起来难度很大,后面转为使用乐鑫预训练好的中文识别模型来实现语音识别。
3. ADC按键通过RTOS进程了实时检测并通过队列来发送数据。
4. 超声波测距通过中断来实现,提高检测的准确性。
5. 通过WIFI连接网络实现时间同步和WEB服务功能
6. 由于语音识别、人脸识别还有WIFI这些都是比较占用内存和资源的程序,因此整体通过状态机来控制实现分时运行。整体的代码逻辑最终都放入loop循环中通过状态机及读取时间间隔的形式来实现非堵塞式运行。
硬件框图如下:

五、 调试软件及使用的编程语言说明、软件流程图及关键代码介绍
前面提到使用edgeimpulse来训练人脸识别模型,edgeimpulse支持直接生成Arduino库来快速部署到平台,因此这里使用的是Arduino IDE来实现整个项目。Arduino IDE的安装过程具体不再展示,我这里使用的ESP32核心库版本是3.3.4。软件流程图如下:

代码主要通过判断枚举变量currentState的值来切换语音识别、人脸识别及开门欢迎等不同的代码模块。同时因模块较多,为保证不阻塞主程序,代码中使用了大量时间变量用来判断运行时长和实现动画效果。另外在使用arduino中的ESP32核心库的ESP-SR时需要注意在启动命令识别模式后会阻塞整个循环,要通过时间等进行控制,避免卡顿。
void loop()
{
static unsigned long personDetectedStartTime=0;
int key_id = -1;
if (xQueueReceive(keyEventQueue, &key_id, 0) == pdTRUE) {
Serial.printf("Key %d pressed!\n", key_id);
if( currentState != STATE_INFO ){
currentState = STATE_INFO;
ESP_SR.setMode(SR_MODE_OFF);
ESP_SR.pause();
u8g2.clearBuffer();
u8g2.sendBuffer();
setLedColor(0);
startWiFi();//启动wifi
startWebServer();
}
switch(key_id){
case 0:
loadAndShowLog();
break;
case 1:
deleteLog();
loadAndShowLog();
break;
case 2:
break;
case 3:
if( currentState == STATE_INFO ){
currentState = STATE_ULTRASONIC;
u8g2.clearBuffer();
u8g2.sendBuffer();
stopWebServer();
stopWiFi();//关闭wifi
}
break;
}
}
welcomeLightShow();//更新ws2812状态
updateBuzzer();//更新蜂鸣器状态
updateLedFlash();//更新LED状态
checkAndCloseServo();//更新舵机状态
webServerloop();//更新web服务器
if (millis() - lastTrigger >= 5000) {
sonar.trigger();
lastTrigger = millis();
}
if (sonar.isReady()) {
float d = sonar.getDistanceCm();
if(d>2 && d<80){
personDetectedStartTime = millis();
if(currentState == STATE_ULTRASONIC){
currentState = STATE_FACE_RECOG;
timeOutStart = millis();
}
}else{
if((currentState != STATE_ULTRASONIC) && (currentState != STATE_INFO)){
if(millis()-personDetectedStartTime>10000){//无人则停止认证流程
currentState = STATE_ULTRASONIC;
ESP_SR.setMode(SR_MODE_OFF);
ESP_SR.pause();
u8g2.clearBuffer();
u8g2.sendBuffer();
setLedColor(0);
}
}
if(closeDoor){
closeDoor = false;//关门
currentState = STATE_ULTRASONIC;
setLedColor(0);
openServoBackward();
u8g2.clearBuffer();
u8g2.sendBuffer();
}
}
}
if((currentState != STATE_ULTRASONIC) && (millis() - timeOutStart >totalTimeout*60000) &&(currentState != STATE_INFO)){//超时记录
timeOutStart = millis();//触发后更新超时时间,防止重复触发
setLedColor(3);
abnCount++;
saveLog(abnCount);
camera_fb_t *fb = esp_camera_fb_get();
takePhoto(fb,"/auto_photo.jpg");
esp_camera_fb_return(fb);
}
if(currentState == STATE_FACE_RECOG){
if(!inFaceProcessing){
Serial.println("开始人脸识别");
inFaceProcessing = true;
startFaceProcessingTime = millis();
drawIconString(1, 30, seq, sizeof(seq),2);//正在采集,请对准人脸
setLedColor(1);
}else{
face_reco();
}
}
if(currentState == STATE_VOICE_RECOG){
if(!inVoiceProcessing && millis() - waitforVoiceRecog>1000){//确保灯光动画完成
inVoiceProcessing = true;
drawIconString(30, 30, seq1, sizeof(seq1),5);//正在识别
Serial.println("开始语音识别");
ESP_SR.setMode(SR_MODE_COMMAND);
ESP_SR.resume();
}
if(millis() - waitforVoiceRecog>20000){
ESP_SR.setMode(SR_MODE_OFF);
ESP_SR.pause();
currentState = STATE_FACE_RECOG;
inVoiceProcessing = false;
}
}
if(currentState == STATE_MOTOR_RUN){
if(!closeDoor){
ESP_SR.pause();
drawIconString(30, 30, seq2, sizeof(seq2),5);//欢迎光临
startUnlockSound();
startWelcomeLight();
setLedColor(0);
openServoForward();
closeDoor = true;
timeOutStart = millis();
}
}
}
六、 功能展示图及说明(实物展示、软件或工具调试)
1.模型训练
1.1数据采集及导入
为了方便训练,这里从往上搞了很多照片,便于对比,共计设置了4组人脸和一组未知数据,避免在无人的时候还显示是某个标签。

1.2模型设置
根据教程知道设置训练前置参数,基本什么都不用动,我这里使用的是灰度数据。


1.3数据训练
经过了多轮数据补充和调整,最后效果非常棒。

1.4部署模型
直接选Arduino library即可,可以看到最后已经到第11版了。其实我还训练了语音命令模型,但是因为不便于部署双模型最后未使用。

2.功能展示
2.1检测到有人进入人脸识别,并显示提示信息。

2.2人脸识别提示信息结束后实时显示摄像头画面,方便对准人脸。

2.3人脸识别通过LED灯流水闪烁三次,OLED显示提示信息,进入语音识别阶段。

2.4语音识别通过,舵机动作,OLED显示欢迎信息,WS2812灯带播放动画,蜂鸣器播放音乐。


2.5检测到无人,舵机归为,屏幕清空。

2.6按键进入查看信息页面并启动WIFI和WEB服务器

2.7进入WEB服务器后可修改参数、查看实时照片、查看异常记录信息和照片等。




七、 项目中遇到的难题及解决方法
1. u8g2崩溃处理
项目中使用OLED屏是通过u8g2库驱动的,在初步将u8g2整合进项目时出现了只要开启u8g2显示,就会导致内存崩溃的异常问题。将异常抛给AI,一一核对验证后确认是u8g2在显示文字前必须设置字体,否则会导致崩溃,不像LVGL有默认字体。
2. WIFI连接不上
因为项目中用到了WEB服务器,主控需要连接网路。在测试中发现主控运行扫描程序时能正常扫描到网络,但是连接时一直提示找不到对应的接入点,只有和AP靠的很近,外加运气加持的情况下才能连接成功。自己也排查了软件设置、天线是否连接等问题,都没能解决。也通过学习群获得了群友的帮助和提醒,但是问题依旧。后来清空flash烧录单独的WIFI连接程序后正常,再烧录项目程序后还是不能正常连接手机、电脑等分享的网络,但是可以连接路由器的网络。所以只能确认是固件问题,但是根本原因未知(应该是语音识别部分的),先避开使用手机、电脑的共享网络。

扫描程序显示AP信号很强

执行连接时无法找到AP
3. 语音识别导致主程序卡顿问题
在项目整合阶段所有功能模块单独测试都正常运作,但是一合并发现出现灯光动画被打断、OLED显示画面不全等问题。这些单独功能模块中只有语音识别是后台运行-看不到运行状态的,因此拿他单独测试。测试程序中只有语音识别,然后在主循环里面循环打印字符,查看打印信息发现在语音识别进入命令识别阶段有5-6秒的时间会堵塞主循环,而且设置了语音识别为OFF模式也会出现这种情况,再查看语音识别库的语音识别API,里面有提供暂停和恢复语音识别任务的函数,增加这个函数外加时间轮询控制后整个工程方正常。
4. 舵机与灯带引脚冲突
在单独进行舵机与灯带测试时,两个例程和模块均能正常工作,而植入最终程序后要么灯带不工作,要么舵机不工作。后来仔细看原理图才发现,这两个模块用的是同一个引脚,真是粗心大意害死人,以后写程序前一定要线看原理图。之后更换灯带到温湿度引脚发现还是不能正常工作,直到换至光敏电阻引脚方才正常,难道GPIO3不能用于驱动WS2812吗,没搞明白。
5. ADC按键超量程
测试ADC按键时发现第三个和第四个按键均达到了ADC值4095,实际查看原理图发现按键是连接在5V电压上的,而本身电阻分压不足,导致主控读取到的电压已超过其量程。之后通过铁板烧的形式将第一颗分压电阻改为了3.3k欧,所有按键均能正常工作了。
八、 心得体会
通过这次活动我学习到了如何快速的训练、部署简单的自定义模型,多模块状态机如何处理,以及遇到问题的排查方向和思路,提高了个人对嵌入式开发的认识。感谢电子森林组织的这次2026年第6届“寒假在家一起练“活动。