
简介基于树莓派3B与Qt开发的智能家居监控系统毕业设计项目面向嵌入式方向本科生及C/C课程设计人群也适合物联网技术爱好者作为实战参考。系统整合DHT11温湿度采集、红外探测、摄像头监控、蜂鸣器报警模块并通过Qt设计图形界面完成数据展示与交互控制覆盖从底层驱动到上层UI的完整链路。资源压缩包共35个文件容量19.71MB以C/C源码、Qt工程文件及配套资源为主.h/.c/.cpp文件实现传感器驱动与业务逻辑.pro/.ui/.qrc用于管理Qt项目与界面wav、png、ico分别承担提示音、界面图与应用图标Makefile和配置文件便于快速构建运行。内容包含源码、界面素材及可执行文件经本地编译验证按文档配置环境即可运行。项目难度适中适合作为毕业设计蓝本或课程设计参考目前已有222人学习使用。1. 树莓派3B与QT一套能跑到毕业答辩现场的智能家居监控系统毕设做智能家居选题的同学最怕的不是代码写不出来而是整套系统在答辩现场断电、串口乱码、界面卡死。这套基于树莓派3B和QT的监控系统源码在本地编译过可运行核心逻辑分两层底层用C语言直接操作GPIO读取DHT11温湿度和红外传感器上层用QT绘制监控界面并负责告警联动。文章不是说明书而是按“底层驱动 → 跨线程数据 → QT界面 → 部署验证”的顺序把代码拆开讲清楚每个模块里能直接用的函数、参数和排错经验都会提到。适合正在做嵌入式毕设、或者想快速搭一套可演示的家居监控原型的开发者。2. 系统架构与数据流为什么GPIO采集和QT渲染必须分离2.1 树莓派3B的引脚规划与模块选型树莓派3B的40Pin GPIO是这套系统的物理基础。打开源码里的main.h可以看到引脚宏定义集中在头文件里这是嵌入式项目的好习惯。DHT11接BCM编码的GPIO17红外接收头接GPIO27蜂鸣器接GPIO22摄像头走CSI接口不占GPIO。用BCM编号而不是物理引脚号是为了在wiringPi或sysfs操作时减少换算错误。选型上注意两个点DHT11是单总线协议数据脚要接上拉电阻树莓派内部有弱上拉但外接4.7kΩ到3.3V更稳红外接收头是1838T这类集电极开路输出空闲时输出高电平收到38kHz载波信号后拉低。这两个器件的时序要求完全不同DHT11靠微秒级延时读位红外靠脉宽测量判断0和1所以源码里dht11.c和infrared.c分成两个独立文件避免互相干扰。// main.h 引脚定义节选 #define DHT11_PIN 17 // BCM编号 #define IR_PIN 27 #define BEE_PIN 222.2 共享内存结构体多文件协作的通信枢纽整个C语言控制层不是单文件撸到底而是通过sharing_data.h里的一个全局结构体作为数据中枢。这个设计在毕设答辩时很好讲每个传感器模块只负责写自己的字段服务模块只负责读模块之间零耦合。// sharing_data.h 核心结构体 typedef struct { float temperature; // 温度值 float humidity; // 湿度值 int ir_code; // 红外解码结果 int alarm_flag; // 告警标志位1触发蜂鸣器 int camera_ready; // 摄像头就绪状态 } SensorData;字段定义注意两点ir_code用int而不是char因为红外解码会得到32位 NEC协议码需要区分地址码和数据码alarm_flag是易变数据在实际项目中建议加volatile修饰防止编译器优化导致主循环读不到最新值。这套结构体的命名和类型选择直接影响到后面QT槽函数读数据的方便程度。2.3 二级章节QT界面层与C语言控制层的线程模型QT部分的核心矛盾是界面主线程不能阻塞但GPIO读取天然是阻塞操作。源码的做法不是把传感器读取放到子线程里用QThread封装而是用一个定时器周期性拉取共享结构体。打开smart_home.cpp看MainWindow构造函数能看到关键代码QTimer *timer new QTimer(this); connect(timer, SIGNAL(timeout()), this, SLOT(updateSensorData())); timer-start(1000); // 每1秒刷新一次界面数据这里用SIGNAL/SLOT老式语法而不是MainWindow::updateSensorData函数指针写法是因为这套源码在QT5和QT6下都能编译兼容性更好。刷新周期设1秒是权衡结果DHT11本身采样周期就是1到2秒设太快读到的还是旧值设太慢红外告警无法及时显示。updateSensorData()槽函数内部调用read_sensor_data()这个函数声明在service.h里是连接C语言层和QT层的桥梁。它在service.c中实现了对SensorData结构体的读取并且加了简单的互斥保护——虽然本项目是单线程轮询但养成加锁习惯后续扩展多线程推送时不用重构。3. DHT11温湿度读取与红外解码时序敏感型驱动的关键实现3.1 DHT11单总线时序协议分析DHT11一次完整通信分为主机起始信号、从机响应、40位数据三个段。主机先把数据线拉低至少18ms再释放DHT11检测到起始信号后回传80μs低电平响应然后连续输出40位数据。每一位的判定是看高电平持续时间26~28μs代表070μs代表1。源码dht11.c里的读取函数拆得比较细int read_dht11_data(float *temp, float *humi) { // 1. 发送起始信号 pinMode(DHT11_PIN, OUTPUT); digitalWrite(DHT11_PIN, LOW); delay(20); // 至少18ms digitalWrite(DHT11_PIN, HIGH); delayMicroseconds(30); pinMode(DHT11_PIN, INPUT); // 2. 等待从机响应 int timeout 100; while(digitalRead(DHT11_PIN) HIGH timeout--) { delayMicroseconds(1); } if(timeout 0) return -1; // 3. 跳过80μs低电平 80μs高电平 delayMicroseconds(80); // 4. 读取40位数据每一位先低电平50μs再根据高电平宽度判定0/1 uint8_t data[5] {0}; for(int i 0; i 40; i) { while(digitalRead(DHT11_PIN) LOW); // 等待位起始 delayMicroseconds(40); // 在40μs处采样 data[i/8] 1; if(digitalRead(DHT11_PIN) HIGH) data[i/8] | 1; } // 5. 校验前4字节之和等于第5字节 if((data[0]data[1]data[2]data[3]) ! data[4]) return -1; *humi data[0] data[1]*0.1f; *temp data[2] data[3]*0.1f; return 0; }3.2 时序代码的坑延时函数与内核抢占这段代码最容易踩的坑是delayMicroseconds(40)采样点位置。实际波形中0信号的高电平只维持约26~28μs1信号约70μs在40μs处采样能稳定区分两者。但wiringPi的delayMicroseconds在树莓派上是忙等待实现受Linux内核调度影响极小而标准usleep()可能被睡眠调度打断导致时序错乱这点在毕业设计答辩时经常被问到。另一个坑是返回值-1的处理。如果连续多次读到校验错误可能是接线太长或者接触不良程序里应该有重试机制。service.c的轮询函数里做了3次重试每次间隔100ms三次失败才报错并保留旧值避免温湿度在界面上跳0。// service.c 带重试的读取封装 int read_sensor_data(SensorData *data) { int retry 3; while(retry--) { if(read_dht11_data(data-temperature, data-humidity) 0) { return 0; } delay(100); } return -1; // 三次失败 }3.3 红外解码NEC协议与脉宽测量红外模块用中断方式测量脉宽而不是轮询。NEC协议规定引导码是9ms低电平加4.5ms高电平之后每个数据位是560μs低电平加560μs0或1.69ms1高电平。源码在infrared.c里用了wiringPiISR注册下降沿中断void ir_int_handler(void) { static unsigned long last_time 0; unsigned long now micros(); unsigned long duration now - last_time; last_time now; if(duration 10000) { // 引导码约13.5ms bit_count 0; ir_data 0; return; } if(bit_count 32) { if(duration 1000) { // 高电平脉宽大于1ms视为1 ir_data | (1UL (31 - bit_count)); } bit_count; } }这个处理方式的关键差异在计算逻辑上测量的是上升沿到下降沿的时长所以用下降沿中断记录两次调用间隔。实际判断1的阈值设在1ms比较理想因为0信号的高电平约560μs1信号约1.69ms余量充足。3.4 由红外告警到蜂鸣器联动的状态机拿到红外码后怎么触发蜂鸣器源码在bee.c里没有用复杂状态机而是用一个可配置的“布防/撤防”标志位。界面设置布防后alarm_flag置1红外解码得到指定码时蜂鸣器发声10秒后自动停止。下面是bee.c的核心逻辑// bee.c 告警联动控制 void bee_control(int alarm_flag, int ir_code) { if(alarm_flag ir_code ALARM_CODE) { digitalWrite(BEE_PIN, HIGH); // 蜂鸣器高电平触发 } else { digitalWrite(BEE_PIN, LOW); } }ALARM_CODE可以在main.h里修改比如遥控器上某个常用键的编码。实际演示的时候可以加一个延时关闭策略在告警响起3秒后自动清除ir_code否则下一次循环会把同一条红外码再次触发。不过这个细节源码里没有做可以在移植时自己补上。4. QT界面与multi-thread交互从x86模拟到树莓派部署4.1 QT界面布局与动态传感器显示工程文件smart_home.pro里包含的模块是core gui widgets没有用QML说明整个界面基于QWidget框架。主界面主要包含三块左侧树形设备列表、中间实时数据显示面板、右侧告警日志区。核心控件是QLCDNumber用来显示温度和湿度数值这种控件在答辩现场视觉效果好字体大且辨识度高。数据更新用定时器拉取但界面标签的更新方式值得注意。源码在smart_home.cpp里的更新函数不是直接ui-tempLabel-setText(...)而是先格式化再赋值void MainWindow::updateSensorData() { SensorData data; if(read_sensor_data(data) 0) { ui-tempLCD-display(QString::number(data.temperature, f, 1)); ui-humiLCD-display(QString::number(data.humidity, f, 1)); logMessage(QString(温度: %1℃ 湿度: %2%%).arg(data.temperature).arg(data.humidity)); } }这里的QString::number(data.temperature,f,1)保留一位小数因为DHT11分辨率只有1位小数。logMessage是自定义函数往QTextEdit里追加一条带时间戳的日志这样就形成了一整套可见的更新证据链。4.2 信号槽机制与线程安全边界这套架构最大的结构特点是串口/GPIO读取全部在C语言层完成QT侧只用定时器读结果结构体。好处是避免了直接在子线程里操作UI控件也就回避了QT线程模型里QObject不能在非主线程直接setText的典型错误。如果后续要改成线程推送模式正确做法是子线程发射信号主线程槽函数更新。// 在QThread子类中发信号主线程槽函数处理避免跨线程操作UI void SensorThread::run() { while(!isInterruptionRequested()) { SensorData data; if(read_sensor_data(data) 0) { emit dataReady(data); // 跨线程信号 } msleep(1000); } }注意信号dataReady必须先在类头部用signals:声明且参数是SensorData结构体值传递而不是指针。这里用值传递是因为结构体本身只有16字节拷贝代价极小而指针可能因为发送方生命周期结束导致悬空。4.3 QT工程在x86与ARM之间的编译环境切换同一套源码想要先在PC上跑通再部署到树莓派3B工程文件里需要有条件编译。smart_home.pro里用unix:!macx区分平台但实际跳转逻辑还依赖于main.cpp里对平台宏的判断#ifdef __arm__ // 树莓派上运行初始化wiringPi和GPIO if(wiringPiSetup() 0) return -1; init_sensors(); #else // x86调试模式用模拟数据替代真实传感器 enable_simulator(); #endif这样PC上调试时read_sensor_data返回模拟数据QT界面和逻辑部分可以全流程跑通换到树莓派编译时自动切换到真实GPIO路径。常见的打包部署方式是在树莓派上安装qt5-default和libqt5widgets5然后直接qmake加make构建。如果为省时间想用x86虚拟机交叉编译需要装树莓派工具链并修改smart_home.pro里的QMAKE_CXX相比之下直接板载编译方案噪音最小。4.4 资源文件与图标让程序脱离源码也能跑项目里包含ziyuan.qrc、smart_home.qrc、myapp.ico和myapp.rc其中两个qrc文件对应相同资源逻辑一个用于QT资源系统管理图片和样式表另一个做应用图标挂载。smart_home.qrc通常在编译时嵌入二进制文件而myapp.rc是Windows平台资源描述文件用于生成exe图标在Linux下编译时会被忽略。这个设置说明资源的源文件本身兼容跨平台。界面图片加载方式决定了打包时的差异如果用qrc嵌入QT会编译进二进制单独拷贝exe也能显示图片如果直接用相对路径加载图片运行时必需带文件夹。源码里用的是qrc方式所以最终部署时只需拷贝生成的二进制文件和相关动态库不用额外携带图片目录。5. 摄像头采集与多传感器数据融合CSI接口和内存映射IO5.1 摄像头初始化与采集参数配置camera.c是这套监控系统的视觉模块。树莓派CSI接口的摄像头不适合用OpenCV读/dev/video0的V4L2方式直接操作因为raspivid或libcamera走的是专用GPU路径普通V4L2兼容层在3B上性能欠佳。源码在camera.c中直接调用raspistill命令行工具并配合帧缓冲显示// camera.c 调用系统命令拍照并存储 int capture_image(const char *path) { char cmd[128]; snprintf(cmd, sizeof(cmd), raspistill -o %s -w 640 -h 480 -q 85, path); int ret system(cmd); if(ret 0) { // 通过framebuffer刷新图像到LCD display_image_to_lcd(path); } return ret; }-w 640 -h 480限制分辨率有意义监控场景不需要全分辨率640x480既能看清室内环境单张JPEG大小控制在80KB以内也方便存入SQLite或直接以base64编码发送到QT界面。-q 85是JPEG质量参数85能在清晰度和文件大小之间取得平衡。拍摄间隔按需调用不建议在主循环里频繁执行因为每次拍摄约耗时1.5秒。5.2 视频预览与照片抓拍并存的界面联动QT界面上如果只显示静态照片答辩演示效果会降低因为评审想看“实时”监控。常见做法是QT侧用QCamera或简单的帧读取方式显示视频流拍照时调用capture_image。但源码选择了一个轻量方案摄像头截图保存到固定路径QT定时器读取这个JPEG显示。// QT侧读取JPEG显示到QLabel void MainWindow::updateCameraFrame() { QPixmap pixmap(/tmp/snapshot.jpg); if(!pixmap.isNull()) { ui-cameraLabel-setPixmap(pixmap.scaled( ui-cameraLabel-size(), Qt::KeepAspectRatio)); } }摄像头路径硬编码在/tmp/snapshot.jpg每次raspistill覆盖写。这套设计的缺陷是“刷新率”受限于拍照时间实测大概1秒一帧但作为监控演示足够。需要更高帧率的话可以改成在C语言层用mmalAPI写一个视频采集线程通过共享内存把帧数据传给QT的QImage绘制。5.3 多传感器数据融合异常综合判定逻辑温湿度、红外、摄像头不是各管各的而是要在service.c里做异常综合判断。比如此时温度超过38℃且红外检测到有人活动则判定为高温告警温度正常但湿度低于30%且无人活动则只写日志不触发蜂鸣器。// service.c 多传感器融合判决 void evaluate_environment(SensorData *data) { if(data-temperature 38.0f) { >sudo apt update sudo apt install qt5-qmake qtbase5-dev libqt5widgets5 libqt5core5a cd RQ_HMS-master qmake smart_home.pro make -j4 ./smart_home-j4利用树莓派3B的四核并行编译首次编译时间约2分钟。qmake之后如果报project ERROR: Unknown module(s) in QT: widgets说明qtbase5-dev没装全。编译OK后运行程序若提示找不到libwiringPi.so.2.50需要执行sudo apt install wiringpi或从源码编译wiringPi库。6.2 串口日志与错误排查技巧程序运行后如果DHT11读不到数据优先查看内核日志和GPIO状态。使用gpio readall检查引脚电平变化再用dmesg | tail -20看驱动报错。代码层面可以用fprintf(stderr, ...)打印中间变量的诊断信息但建议把调试开关作为编译选项集中控制#ifdef DEBUG_MODE #define DBG_PRINT(fmt, args...) fprintf(stderr, fmt, ##args) #else #define DBG_PRINT(fmt, args...) do {} while(0) #endif在smart_home.pro里加一行DEFINES DEBUG_MODE开启调试不想要时注释掉即可。这种“编译期插桩”方式不会影响发布代码的执行效率也不需要在运行时频繁改动源码。6.3 开机自启动与QT程序运行环境最后一步是把这套系统做成开机自启动的演示环境这对答辩非常加分。在/etc/rc.local里添加启动脚本前先确保程序能在无桌面环境下运行或者跟随桌面自动启动。推荐写一个start_smart_home.sh脚本#!/bin/bash export DISPLAY:0 export QT_QPA_PLATFORMeglfs cd /home/pi/RQ_HMS-master ./smart_homeQT_QPA_PLATFORMeglfs在树莓派上不使用X11窗口系统而是直接走OpenGL ES显示能获得更低延迟和更流畅的界面刷新。如果用的是官方桌面环境考虑把该变量去掉让程序在LXDE桌面上显示为标准窗口。脚本存放在/home/pi/目录chmod x后sudo追加到/etc/rc.local的exit 0之前重启机器后系统会在登录后自动拉起监控程序。另一个可行的验证方式是用systemd服务管理自启动比rc.local更可控且支持崩溃自动重启服务文件里的Restartalways参数足够覆盖演示期间进程意外退出的情况。6.4 复现陷阱与参数速查表最后放一个执行流程中各环节的参数速查表方便排查问题时对照检查环节关键参数或命令常见错误DHT11接线BCM17上拉4.7kΩ引脚号写错读到恒定-1红外解码下降沿中断阈值1000μs判1触发条件在x86模拟器下无法验证蜂鸣器BCM22高电平触发GPIO初始化失败则无输出QT定时器刷新周期1000msSIGNAL/SLOT语法槽函数未声明导致编译中断摄像头raspistill -w 640 -h 480 -q 85设备树未启用则报no camera detected自启动QT_QPA_PLATFORMeglfs无显示器时无法启动窗口需改用offscreen平台本文还有配套的精品资源点击获取