作者:Crail
来自:Fab academy
在数字化时代,科技与人文关怀的结合愈发紧密。今天,小编将给大家带来一位Fab Academy毕业生 Crail 的结项项目:Indoor People Existence Fall Detection,这个项目不仅在视觉上呈现了太极图案的和谐之美,同时在功能上实现人员摔倒监测提醒。让我们深入了解一下这个项目。
创作背景
外观创意背景:
我初步决定我的最终项目是“离别与重逢之美”。从数学几何的角度来看,我们可以轻松地将一个正方形分成不同的部分,使整个形状看起来像太极图案。分割图形中的每个小图形都连接到相邻图形。通过旋转图形的中心,您可以看到散布在中心的 8 个碎片图案。在最后一次旋转后,形成新的六边形图案。
我想通过这种数学几何模式的转换来表达中国文化中的分裂和联合的概念。
功能创意背景:
具体的功能灵感需求来自我在春节期间回家的时候。当我回到家时,我发现我的祖父母一个人住,经常有他们无法照顾的事情。老年人腿脚的不便,会对老年人的日常健康产生一定的影响。为了更好地照顾老年人的家庭健康,我决定使用一些物联网监控解决方案。
功能介绍
人员跌倒检测功能:
对于另一个功能,我希望在白天,当整个设备打开时,可以通过中间的毫米波传感器检测室内人员的运动状态,作为防摔监测功能。
远程报警消息:
最后,设备可以将监测结果发送到云端或手机,提醒人们监测家中老人是否跌倒。
参考链接:https://fabacademy.org/2024/labs/chaihuo/students/crail-lyu/projects/final-project/
材料清单
数量 | 描述 | 价格 |
---|---|---|
1 | Seeed Studio XIAO ESP32C3 | 4.99$ |
1 | Seeed MR60FDA1 60GHz毫米波传感器 | 37.00$ |
1 | DRV8833电机驱动模块 | 2.00$ |
1 | N20* 65r/min直流电机 | 5.00$ |
1 | 24-WS2812 LED环形灯 | 6.00$ |
1 | 2*3针SMD插座 | 0.30$ |
1 | 2*4针SMD插座 | 0.30$ |
1 | M3*8mm螺柱 | 2.00$ |
10 | M3*22mm螺丝+M3螺母 | 5.00$ |
14 | 3mm亚克力板 | 6.00$ |
1 | 跳线 | 0.30$ |
1 | 总成本 | 69.19$ |
项目规划
阶段 | 周 | 日期 | 任务 | 里程碑 |
---|---|---|---|---|
规划与构思 | 第一周 | 5 月 6 日至 5 月 12 日 | - 定义项目目标- 收集所有必要的组件- 安装和配置Arduino IDE- 研究并选择合适的材料 | 5月10日:完成初步准备 |
机械设计 | 第二周 | 5 月 12 日至 5 月 19 日 | - 2D/3D设计原型 - 调试和优化2D和3D设计- 组装机械零件 | 5月17日:完成机械零件 |
电子设计 | 第三周 | 5 月 19 日至 5 月 26 日 | - 开发原理图设计和PCB设计- 制造PCB板 - 测试和调试PCB板 | 5月23日:解决PCB问题 |
软件设计 | 第四周 | 5 月 26 日至 6 月 2 日 | - 开发传感器- 调试和完善 MQTT 发布的代码- 优化性能和功耗 | 6月1日:经过全面测试和调试的系统 |
项目完结 | 第五周 | 6 月 2 日至 6 月 6 日 | - 完成功能(软件和硬件)- 组织材料(视频和照片)- 准备最终演示和文档 | 6月6日:项目完成并准备展示 |
接线图
根据Wiki网页将引脚进行连接:
原理图设计
在设计原理图时,我没有直接连接各种设备。为了方便后续检查,我采用了模块化设计,并设置了标记编号来连接它们,以方便错误检查。
PCB布局设计
在设计PCB时,由于我们直接使用原理图进行布线,因此我仍然保留了XIAO ESP32模块。我在电路板上画了一个凹口来夹住微控制器,然后通过旁边的焊盘将其固定。主控制器与其他设备之间的连接。PCB布线过程是一个相对耗时的过程。它必须符合规则,以便所有线条放在一起而不会相互干扰,并且板子必须美观整洁。
电子零件制造
我通过 kicad 的导出工具将 PCB 导出到相应的制造文件。总共导出了三个文件。trace, drill 和 cut三个GBR格式文件分别对应PCB加工的表面加工、钻孔加工和切边加工。
PCB铣削
第 1 步:我需要通过 Gerber2PNG 工具将所有制造项目 GBR 文件转换为 PNG,如下图所示。
第 2 步:导入制造文件时,您可以看到显示的整个 PCB 图案。
第 3 步:我们需要通过选择和输出不同的文件来获得 PCB 走线、钻孔和切割的三个单独的制造和加工 PNG 图像。
第 4 步:通过 modsproject 网站发送生成的制造文件,并输出到相应的数控加工文件中进行数控加工。这里需要注意的是,需要选择PNG文件的分辨率,以及钻孔过程的刀具速度和深度。
第 5 步:将以下成功转换的 NC 加工文件导入数控机床进行加工。
第 6 步:最终生产的电路板如下
调试、仿真与测试电路板
在项目中,我没有对电路板的功能进行虚拟程序模拟。我只是在画完图纸后检查了设计规则。这是 ERC 运行 resulf 与 faluts:
这也导致了我致命的设计错误。由于我没有仔细模拟和检查,当我实际制作电路并下载测试程序时,我才发现我已经将主传感器的RX和TX接口与XIAO的RX连接在一起。,TX接口连接不正确,导致我不得不使用飞线进行修改才能发现错误,然后在随后的二次制造中进行修改。这个过程浪费了我大量的时间和精力。
这是一个常见的错误,我将微控制板与传感器的RX&TX端口错误的连接。正确的连接方式如下图所示:
在我的导师萨尔曼的帮助下,他帮助我焊接了跳线,使其以正确的方式连接。
最后,当我用笔记本电脑连接电路板时,从串行监视器显示的结果如下:
优化
这部分的优化方案是我测试并安装第一个版本后决定的最终电路优化方案。
在最终版本中,我对PCB进行了以下优化:
- 增加走线宽度:为了使处理后的电路走线更加明显,我将电路走线的宽度从0.8mm增加到1mm;
- 调整连接端口:为了方便接线,我计划在后续改动中使用XIAO ESP32自带的软串口,因此修改了传感器的连接接口,增加了额外的焊盘接口;
- 调整标记丝印的大小和厚度:为了使图案更明显,我修改了丝印的位置和大小;
- 在PCB板上加固定孔:为了使以后的板子和结构件更加稳定,我加了5个M3螺丝定位孔;
- 将传感器的SMD插座改为直插式插座:为了方便传感器的固定和焊接,从而更便于信号的稳定传输,我修改了插座型号。
外观设计与制造
一开始,我想根据我最初的想法做一个开闭的艺术装置,但后来我发现很难制作和处理一个离散的图案。
最后,我参考了许多打开和关闭装置的机构,并选择了一种用于生产和加工。起初,我只是根据PCB的尺寸绘制设备,以实现旋转开合功能。
最终的完整机械结构通过旋转和固定,可以打开和关闭设备,并可以使用泄漏传感器进行测量。
电子传动结构图
这种传动机构实际上是整个设计中最困难的部分。我需要解决 3 个问题:
1.如何将电机固定在整个机构上?
设计了第三个版本,通过设计电机固定孔来固定电机;
2.如何使电机转动使整个机构转动?
设计了一种齿轮啮合机构,最外层的启闭机构必须旋转才能完成转动。
3..如何使齿轮啮合机构更加完善?
我最初想使用Solidworks附带的尺寸生成工具,但由于我需要的尺寸太小,我只能根据基本尺寸渐近线的规则自己设计。
设备固定
因为我的最终设计是一个可以支撑旋转平面的组件。所以我用3D打印来处理它。我需要设计一个以 25 度角倾斜而不是垂直的支架的底座,在底座前部有 4 个固定的倾斜开口。所以我用3D打印来加工我的底座。
- 我画了一个65毫米*65毫米的立方体,然后把它做成5毫米的厚度将其作为基本平台。
- 我选择了正面,使上升器呈 25 度角站立。
- 将一些直角零件调整到半径为15mm,特别是在支撑部位做弧形支撑,以增加底座的稳定性。
- 通过挤压切割制作四个直径为3毫米的圆孔。
存在问题:这4个圆孔不容易清洗因为我是设置在这个3D打印的支持。
最终结构
最终的机械结构如下图所示。对于大多数组件,我使用激光切割机切割 3 毫米亚克力板。经过三次修改,我得到了最终的解决方案,。它使用一个小电机来控制齿轮啮合来控制旋转,我对部件进行组合装置。此外,我还用3D打印制作了设备的支架,并在后面为装饰性LED光环预留了安装位置。
软件功能及实现流程
人体毫米波传感器可以检测以下状态:
- 当传感器正常工作且未检测到任何人时,传感器会报出相对较低的数据值,LED灯环会亮起绿色表示工作状态。
- 当传感器开始检测到有人经过或停留时,传感器会在MQTT的数据栏中上报数据值并给出文字提醒,LED灯环会亮起黄色,表示检测到有人。
- 当传感器检测到有人出现或停留且持续时间超过20秒,传感器会提醒有人跌倒,并会发出远程报警信息,LED灯环会亮起红色,表示跌倒检测状态。
- 当人体毫米波传感器长时间没有检测到任何物体时,它可以通过旋转电机来关闭传感器的开闭机构,进入睡眠模式。当然,人们也可以通过手机远程控制开关。
- LED光环将显示传感器的工作状态和检测到的信息。它应该显示不同颜色的黄色/红色/绿色,以分别显示传感器前有人在场、有人跌倒或没有人的三种状态。
- 装置应该向手机发送报警数据,并留下传感器记录以检查状态。
- 装置应该可以实现远程控制旋转部分的打开或关闭。
软件工作流程:
代码实现
本程序旨在使用 60GHz 雷达传感器执行跌倒检测,并根据通过 MQTT(消息队列遥测传输)接收的命令控制电机。该系统还使用 LED 灯条直观地指示雷达传感器检测到的状态。
详细工作原理:
初始化: setup()
- 串行通信:初始化用于调试 (Serial.begin(115200)) 和雷达传感器 (mySerial.begin(115200)) 的串行通信。
- WiFi设置:使用提供的SSID和密码连接到WiFi网络。
- MQTT 设置:连接 MQTT 代理,设置服务器,订阅电机控制主题。
- LED 灯条设置:初始化 LED 灯条并设置其亮度。
- 电机控制设置:配置电机控制的引脚,并将电机的初始状态设置为关闭。
主循环 :loop()):
- MQTT 连接检查:确保设备保持与 MQTT 代理的连接。如果断开连接,它会尝试重新连接。
- 雷达数据处理:持续检查雷达数据并对其进行处理。如果有雷达报告,它会相应地处理报告。
处理雷达报告(handleRadarReport(uint8\\\_t报告)):
- 解释:解释雷达报告以确定是否有人、某人或特定活动(例如,跌倒、移动)。
- MQTT 消息传递:根据雷达报告向不同的 MQTT 主题发布相应的消息。
- LED 指示:根据检测到的活动类型设置 LED 灯条的颜色。
- 延迟:引入短延迟以防止 LED 灯条快速闪烁。
MQTT 回调 (mqttCallback(char topic, byte payload, unsigned int length)):
- 消息解析:解析传入的 MQTT 消息并将其转换为字符串。
- 电机控制:如果消息用于电机控制,则它会根据消息内容(“ON”或“OFF”)运行或停止电机序列。
电机控制序列:
- 运行电机序列 (runMotorSequence()):
- 逐步提高电机速度,然后将速度反转回停止。
- 停止电机 (stopMotor()):以增量递增降低电机速度,然后将速度反转回停止。
- 设置电机速度 (set\\\_motor\\\_pwm(int pwm, int IN1\\\_PIN, int IN2\\\_PIN) 和 set\\\_motor\\\_current(int pwm\\\_A)):
- 使用PWM信号控制电机速度和方向。
- 设置电机转速并将状态打印到串行控制台。
MQTT 发布 (publishMQTT(const char* topic, String message)):
- 确保 MQTT 连接处于活动状态。
- 将消息发布到指定的MQTT主题。
WiFi 设置 (setup\\\_wifi()):
- 连接到 WiFi 网络并在连接后打印 IP 地址。
MQTT 重新连接 (reconnect()):
- 如果连接丢失,则尝试重新连接到 MQTT 代理。
- 重新连接成功后订阅必要的 MQTT 主题。
LED 控制 (setColor(uint32\\\_t color)):
- 将灯带中所有 LED 的颜色设置为指定颜色并更新灯带。
电机速度控制 (spin\\\_and\\\_wait(int pwm\\\_A, int duration)):
- 将电机速度设置为指定值并等待指定的持续时间。
工作原理总结:
该程序初始化各种组件(WiFi、MQTT、雷达、LED、电机),然后在循环中连续运行。它确保持续的 MQTT 连接,处理雷达数据以检测人类的存在或跌倒,向 MQTT 主题发布相关消息,控制 LED 灯条以获得视觉反馈,并根据传入的 MQTT 命令控制电机。
具体完整代码如下:
#include "Arduino.h"
#include <60ghzfalldetection.h>
#include
#include
#include
#include
// Define the pins for SoftwareSerial
#define RX_Pin A2
#define TX_Pin A3
#define LED_PIN 9
// Define the control inputs for a single motor
#define MOT_A1_PIN D0
#define MOT_A2_PIN D1
// MQTT Broker Details
const char* ssid = "Crail"; // WiFi Name
const char* password = "4646995a"; // WiFi Password
const char* mqtt_server = "mqtt.fabcloud.org"; // MQTT Broker Name
const char* mqtt_username = "fabacademy"; // MQTT Username
const char* mqtt_password = "fabacademy"; // MQTT Password
const char* mqtt_topic = "fabacademy/chaihuo/crail/sensor"; // MQTT Topic
const char* mqtt_topic1 = "fabacademy/chaihuo/crail/rawdata"; // MQTT Topic1
const char* mqtt_topic_fall = "fabacademy/chaihuo/crail/fall"; // MQTT Topic for Fall
const char* mqtt_topic_motor = "fabacademy/chaihuo/crail/motor"; // MQTT Topic for Motor Control
WiFiClient espClient;
PubSubClient client(espClient);
SoftwareSerial mySerial(RX_Pin, TX_Pin);
FallDetection_60GHz radar = FallDetection_60GHz(&mySerial);
Adafruit_NeoPixel strip = Adafruit_NeoPixel(24, LED_PIN, NEO_GRB + NEO_KHZ800);
bool motorSequenceExecuted = false;
void setup() {
Serial.begin(115200);
mySerial.begin(115200);
setup_wifi();
client.setServer(mqtt_server, 1883);
client.setCallback(mqttCallback);
client.subscribe(mqtt_topic_motor);
strip.begin();
strip.setBrightness(50);
// Motor control setup
pinMode(MOT_A1_PIN, OUTPUT);
pinMode(MOT_A2_PIN, OUTPUT);
// Turn off motor - Initial state
digitalWrite(MOT_A1_PIN, LOW);
digitalWrite(MOT_A2_PIN, LOW);
}
void loop() {
if (!client.connected()) {
reconnect();
}
client.loop();
radar.HumanExis_Func();
if (radar.sensor_report != 0x00) {
handleRadarReport(radar.sensor_report);
}
}
void mqttCallback(char* topic, byte* payload, unsigned int length) {
String message;
for (unsigned int i = 0; i < length; i++) {
message += (char)payload[i];
}
Serial.print("Message arrived on topic: ");
Serial.print(topic);
Serial.print(". Message: ");
Serial.println(message);
if (String(topic) == mqtt_topic_motor) {
if (message == "ON") {
runMotorSequence();
} else if (message == "OFF") {
stopMotor();
}
}
}
void runMotorSequence() {
Serial.println("Running motor sequence...");
// Ramp speed up.
for (int i = 0; i < 6; i++) {
spin_and_wait(25 * i, 150);
}
// Ramp speed into full reverse.
for (int i = 0; i < 6 ; i++) {
spin_and_wait(135 - 25 * i, 150);
}
// Stop.
spin_and_wait(0, 100);
Serial.println("Motor sequence completed.");
}
void stopMotor() {
Serial.println("Stopping motor...");
for (int i = 0; i < 6; i++) {
spin_and_wait(-25 * i, 150);
}
// Ramp speed into full reverse.
for (int i = 0; i < 6 ; i++) {
spin_and_wait(-135 + 25 * i, 150);
}
Serial.println("Motor stopped.");
}
void handleRadarReport(uint8_t report) {
String message;
String message1;
uint32_t color;
switch (report) {
case NOONE:
message = "Nobody here.";
color = strip.Color(0, 0, 0); // Off
publishMQTT(mqtt_topic, message);
break;
case SOMEONE:
message = "Someone is here.";
color = strip.Color(255, 255, 255); // White
publishMQTT(mqtt_topic, message);
break;
case NONEPSE:
message = "No human activity messages.";
color = strip.Color(0, 0, 0); // Off
publishMQTT(mqtt_topic, message);
break;
case STATION:
case MOVE:
message = "Someone moving or stopped.";
color = strip.Color(255, 255, 0); // Yellow
publishMQTT(mqtt_topic, message);
break;
case BODYVAL:
message1 = String(radar.bodysign_val, DEC);
color = strip.Color(0, 255, 0); // Green
publishMQTT(mqtt_topic1, message1);
break;
case FALL:
message = "Someone fell!";
color = strip.Color(255, 0, 0); // Red
publishMQTT(mqtt_topic, message);
publishMQTT(mqtt_topic_fall, message);
break;
}
Serial.println(message);
if (report == BODYVAL) {
Serial.println(message1);
}
setColor(color); // Set LED color
delay(200); // Delay for 0.2 seconds to prevent rapid flashing
}
void publishMQTT(const char* topic, String message) {
if (!client.connected()) {
reconnect();
}
client.loop();
if (client.connected()) {
client.publish(topic, message.c_str());
}
}
void setup_wifi() {
delay(10);
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
}
void reconnect() {
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
String clientId = "XIAO-ESP32-Client-";
clientId += String(random(0xffff), HEX);
if (client.connect(clientId.c_str(), mqtt_username, mqtt_password)) {
Serial.println("connected");
client.subscribe(mqtt_topic_motor); // Subscribe to the motor control topic
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
delay(5000);
}
}
}
void setColor(uint32_t color) {
for (uint16_t i = 0; i < strip.numPixels(); i++) {
strip.setPixelColor(i, color);
}
strip.show();
}
/// Set the current on the motor using PWM and directional logic.
///
/// \param pwm PWM duty cycle ranging from -255 full reverse to 255 full forward
/// \param IN1_PIN pin number xIN1 for the given channel
/// \param IN2_PIN pin number xIN2 for the given channel
void set_motor_pwm(int pwm, int IN1_PIN, int IN2_PIN) {
if (pwm < 0) { // reverse speeds
analogWrite(IN1_PIN, -pwm);
digitalWrite(IN2_PIN, LOW);
} else { // stop or forward
digitalWrite(IN1_PIN, LOW);
analogWrite(IN2_PIN, pwm);
}
}
/// Set the current on the motor.
///
/// \param pwm_A motor A PWM, -255 to 255
void set_motor_current(int pwm_A) {
set_motor_pwm(pwm_A, MOT_A1_PIN, MOT_A2_PIN);
// Print a status message to the console.
Serial.print("Set motor A PWM = ");
Serial.println(pwm_A);
}
/// Simple primitive for the motion sequence to set a speed and wait for an interval.
///
/// \param pwm_A motor A PWM, -255 to 255
/// \param duration delay in milliseconds
void spin_and_wait(int pwm_A, int duration) {
set_motor_current(pwm_A);
delay(duration);
}