去年疫情期间,我在某商场门口看到一个简单但实用的人流统计装置,触发了我用51单片机实现类似功能的兴趣。这个系统核心功能很简单:通过红外传感器检测人员进出,实时统计区域内人数并在LCD屏显示,当人数超过设定阈值时触发蜂鸣器报警。虽然市面上有成熟的商业解决方案,但自己动手实现不仅能深入理解原理,还能根据实际需求灵活调整。
选择51单片机(STC89C52)作为主控主要基于三点考虑:首先作为经典8位MCU,其GPIO、定时器等外设完全满足本项目需求;其次开发环境(Keil uVision)和编程语言(C51)都是嵌入式领域的标准配置;最重要的是,整套硬件成本不超过50元,非常适合学生和爱好者练手。系统工作电压5V,可通过USB电源或9V电池配合7805稳压模块供电。
提示:实际部署时建议采用双红外传感器对射方案(后文会详细说明),相比单传感器能更准确判断进出方向,避免重复计数。
最初我测试了三种常见的人体检测方案:
最终选择常规红外对管(型号E18-D80NK)的原因有三:首先其有效检测距离80cm完全满足门禁等场景;其次调制型红外发射可有效抑制环境光干扰;最重要的是价格仅4元左右,性价比突出。该传感器输出数字信号(检测到物体时输出低电平),直接连接单片机IO口无需额外电路。
LCD1602字符液晶模块是最经济实用的选择,相比数码管能显示更多信息(16x2个字符),且功耗仅1mA左右。注意市场上常见的有5V和3.3V两种电压版本,务必选择5V兼容的型号。接线时除了数据线(D0-D7),还需要控制线(RS,RW,EN)和背光电源。
蜂鸣器选用有源型(内部带振荡电路),只需给电就会以固定频率鸣响,驱动简单。通过一个NPN三极管(如S8050)连接单片机P3.7口,当IO输出高电平时导通蜂鸣器电路。电阻选择1kΩ限流即可。
完整电路原理图包含以下几个关键部分:
注意:P0口作为开漏输出,必须接10kΩ上拉电阻才能正常驱动LCD。实际焊接时可使用排阻节省空间。
上电后需要依次初始化各外设模块:
c复制void System_Init() {
EA = 1; // 开启总中断
EX0 = 1; // 允许INT0中断
IT0 = 1; // 设置INT0为边沿触发
LCD_Init(); // LCD初始化
LCD_Display(0); // 显示初始计数0
Buzzer = 0; // 确保蜂鸣器初始关闭
}
关键点在于中断配置——将红外传感器连接到INT0(P3.2)并配置为下降沿触发,这样当有人经过阻断红外线时,传感器输出从高变低会产生中断,确保不遗漏任何经过事件。
原始方案存在两个明显问题:一是单传感器无法区分进出方向;二是快速通过时可能重复计数。改进后的算法如下:
c复制unsigned int people_count = 0;
bit last_state = 1;
void EX0_ISR() interrupt 0 {
static unsigned long last_time = 0;
unsigned long current_time = millis();
if(current_time - last_time > 200) { // 200ms消抖
if(IR_Sensor == 0 && last_state == 1) {
people_count++;
LCD_Display(people_count);
if(people_count > THRESHOLD) {
Buzzer = 1;
delay(1000);
Buzzer = 0;
}
}
last_state = IR_Sensor;
last_time = current_time;
}
}
此处引入三个关键改进:
millis()函数实现精准计时消抖(需配置定时器1产生1ms时基)last_state变量记录上次传感器状态,只在下降沿触发计数实际应用中,阈值应该根据场所面积和防疫要求动态调整。我在程序中增加了按键调整功能:
c复制#define THRESHOLD_INIT 50
unsigned char threshold = THRESHOLD_INIT;
void Key_Process() {
if(KEY_UP == 0) { // 假设P1.0接增加按键
delay(10);
if(KEY_UP == 0) {
threshold += 5;
LCD_ShowThreshold(threshold);
}
}
// 同理处理减少按键...
}
报警策略也可扩展为多级响应:
实测发现传感器安装角度直接影响检测可靠性。最佳实践是:
现场调试时遇到的主要问题及解决方案:
为适应电池供电场景,可通过以下措施降低功耗:
c复制PCON |= 0x01; // 进入IDLE模式
实测优化后系统待机电流从35mA降至0.5mA,两节18650电池可连续工作3个月以上。
在出入口各安装一个传感器,通过触发顺序判断进出方向:
c复制if(SensorA先触发) {
count++; // 进入
} else if(SensorB先触发) {
count--; // 离开
}
需配合状态机处理各种可能的事件序列,此处不再赘述。
添加ESP8266 WiFi模块可将数据上传至服务器:
c复制void Send_Data() {
UART_SendString("AT+CIPSTART=\"TCP\",\"api.xxx.com\",80\r\n");
UART_SendString("AT+CIPSEND=50\r\n");
UART_SendString("GET /update?count=");
UART_SendNumber(people_count);
UART_SendString("\r\n");
}
在复杂场景下,可增加OV7670摄像头模块拍摄触发时的图像,通过SD卡存储证据。需要特别注意51单片机有限的资源(需外接FIFO芯片AL422B缓解内存压力)。
这个项目最让我惊喜的是,原本只是作为练手作品,实际部署在社区活动室后,管理人员反馈统计准确率超过95%。期间遇到最棘手的问题是雨天传感器误触发,后来通过调整安装位置和添加防水罩解决。如果重新设计,我会优先考虑太阳能供电和4G远程传输方案,这对实际应用场景会更友好。