基于Arduino与HX711的智能饮水监测系统:从传感器到蓝牙通信全解析

基于Arduino与HX711的智能饮水监测系统:从传感器到蓝牙通信全解析 1. 项目概述与核心思路最近在整理工作室的旧项目时翻出了一个几年前为学校科技展做的“智能饮水助手”原型。这个项目的初衷很简单当时发现身边很多朋友包括我自己经常一忙起来就忘了喝水一天下来饮水量严重不足。市面上的智能水杯要么价格不菲要么功能花哨不实用。于是我们就想能不能用最基础的电子元件自己动手做一个成本低、功能直接、还能通过手机简单交互的饮水提醒装置这个“智能饮水助手”的核心逻辑非常清晰它本质上是一个基于重量变化的监测系统。我们用一个称重传感器俗称“压力传感器”或“秤传感器”垫在水杯下面实时测量杯子的重量。当你喝水时杯子变轻重量减少的数值就对应了你喝下去的水量。Arduino作为大脑负责读取这个重量数据并进行计算和逻辑判断。最后通过一个蓝牙模块HC-05将数据无线发送到手机上的一个简易AppApp负责记录你的饮水历史并在该喝水的时候通过Arduino控制蜂鸣器发出提醒。整个项目的技术栈非常经典是学习物联网硬件入门的绝佳案例传感器数据采集HX711模块、微控制器编程Arduino、无线通信蓝牙串口。虽然原项目的代码和说明有些零散但经过我的重新梳理和大量实践补充下面我会把硬件连接的所有细节坑点、蓝牙配置的完整流程、以及固件编程中每个函数和参数背后的“为什么”都讲清楚。无论你是刚接触Arduino的学生还是想做一个实用小工具的爱好者跟着这篇指南都能把它完整复现出来。2. 硬件选型与电路连接详解硬件是整个项目的物理基础连接的正确性和稳定性直接决定了后续编程和功能能否实现。这里我们用的都是非常通用且性价比高的模块。2.1 核心元件功能解析Arduino Uno R3项目的控制核心。它负责运行我们编写的程序固件读取传感器数据处理逻辑并控制其他模块。选择Uno是因为其引脚布局清晰资料丰富非常适合学习和原型开发。HX711模块这是一个24位高精度模数转换器ADC芯片模块。称重传感器应变片输出的信号是极其微弱的模拟电压变化Arduino自身的ADC精度10位远远不够。HX711的作用就是将这个微小信号放大并转换成Arduino能够稳定读取的高精度数字信号。它通过两个数字引脚DT和SCK与Arduino通信这种通信协议类似于SPI但更简单。5kg称重传感器通常为铝合金梁式结构上面贴有应变片。当受到压力时其电阻会发生变化从而产生电信号。选择5kg量程是考虑到一个装满水的大水杯重量通常在1-2kg留出足够余量可以保证测量精度和传感器寿命。HC-05蓝牙串口模块这是项目的无线通信枢纽。它本质上是一个串口透传模块意味着它将Arduino的串口通信TX/RX无线化。Arduino发送给它的任何数据它都会通过蓝牙原样发送给已配对的手机反之亦然。HC-05有主从两种模式本项目中使用默认的从机模式等待手机连接。有源蜂鸣器提醒装置。与无源蜂鸣器不同有源蜂鸣器内部自带振荡电路给它一个直流电压就会以固定频率发声控制简单用Arduino的一个数字引脚输出高电平即可驱动。2.2 电路连接步骤与避坑指南连接电路时务必在断电状态下操作。以下是经过优化的连接方案比原图更可靠第一步连接HX711与称重传感器称重传感器一般有4根线红E、黑E-、白S、绿S-。它们需要与HX711模块上的对应端子焊接或通过接线端子压接。红线E - HX711的VCC黑线E- - HX711的GND白线S - HX711的A绿线S- - HX711的A-注意不同厂家传感器的线色可能不同请以产品说明书为准。最保险的方法是使用万用表测量红黑线之间电阻通常约为1000欧姆白绿线之间电阻约为1000欧姆且红-白、红-绿、黑-白、黑-绿之间的电阻都接近500欧姆。第二步连接HX711模块与Arduino这里供电和通信要分开理解供电HX711模块需要电力才能工作。将模块的VCC引脚连接到 Arduino 的5V引脚GND连接到 Arduino 的GND。这为整个HX711模块包括其内部芯片和它正在激励的称重传感器供电。通信将HX711模块的DT数据引脚连接到 Arduino 的A1引脚模拟输入引脚但此处用作数字输入SCK时钟引脚连接到A2引脚用作数字输出。这两个引脚用于同步数据传输。第三步连接HC-05蓝牙模块关键步骤这是最容易出错的地方。HC-05的工作电压是3.3V而Arduino Uno的TX引脚输出是5V电平。直接连接可能会损坏蓝牙模块。HC-05 VCC- Arduino5V。模块本身有稳压电路可以从5V降压到3.3V供自身使用。HC-05 GND- ArduinoGND。HC-05 TXD- ArduinoRX (引脚0)。蓝牙模块的发送端连接到Arduino的接收端。HC-05 RXD-电压分压电路- ArduinoTX (引脚1)。这是核心必须对Arduino发出的5V信号进行分压降至约3.3V再给蓝牙模块。分压电路接法准备一个2kΩ电阻和一个1kΩ电阻接近原方案的3kΩ和6kΩ比例但更常用。将2kΩ电阻一端接Arduino的TX另一端接HC-05的RXD同时这个连接点再接1kΩ电阻到GND。这样HC-05 RXD接收到的电压 5V * (1k / (2k 1k)) ≈ 3.33V安全可靠。第四步连接蜂鸣器蜂鸣器正极长脚或标有“”号 - Arduino数字引脚13通过一个100Ω限流电阻更安全。蜂鸣器负极短脚 - ArduinoGND。连接完成后的整体供电逻辑是USB线为Arduino供电Arduino的5V引脚为HX711和HC-05模块供电。务必检查所有连接确保没有短路特别是5V和GND碰在一起。3. 蓝牙模块HC-05的配置与AT指令详解很多新手会跳过这一步直接烧录主程序结果发现手机搜不到蓝牙或者无法通信。对HC-05进行初始配置是必不可少的一步目的是给它设置一个容易识别的名字、配对密码和合适的通信速率。3.1 配置电路搭建配置时我们需要通过Arduino向HC-05发送AT指令。此时HC-05的RXD引脚需要接收来自Arduino TX的AT指令但同时又要避免与主程序冲突。因此我推荐使用一个更稳妥的“专用配置电路”方法而不是在原电路上折腾从你的项目主电路中暂时取下HC-05模块。单独准备一个Arduino或者就用同一个但先拔掉所有其他连线按如下方式连接HC-05 VCC - Arduino 5VHC-05 GND - Arduino GNDHC-05 TXD - Arduino RX (引脚0)HC-05 RXD - Arduino TX (引脚1)注意这里不需要分压电路因为此时蓝牙模块可能未启动或者我们利用其进入AT模式的不同电压特性。更通用的方法是直接连接但将HC-05的KEY/EN引脚置高来进入AT模式。关键操作找到HC-05模块上的一个小按钮或一个标有“KEY”或“EN”的引脚。在不给模块通电的情况下按住这个按钮不放然后给模块上电连接Arduino 5V。此时模块上的LED会变为慢闪例如2秒一闪这表示它进入了AT命令模式此时通信波特率固定为38400。3.2 使用Arduino IDE进行AT指令配置打开Arduino IDE上传下面这个极其简单的“AT指令转发”程序。这个程序的作用是把电脑通过USB发送给Arduino的字符原样转发给HC-05并把HC-05的回复显示给电脑。void setup() { Serial.begin(38400); // 电脑与Arduino的通信速率 Serial1.begin(38400); // Arduino硬件串口1引脚0,1与HC-05的通信速率 } void loop() { // 从电脑读取指令发送给HC-05 if (Serial.available()) { Serial1.write(Serial.read()); } // 从HC-05读取回复发送给电脑 if (Serial1.available()) { Serial.write(Serial1.read()); } }上传后打开串口监视器工具 - 串口监视器。确保右下角波特率设置为38400行尾选择“Both NL CR”即新行和回车。现在你通过串口监视器输入的任何命令都会直接发送给HC-05。3.3 核心AT指令序列在串口监视器的输入框里依次输入并发送以下命令每条指令后HC-05都应回复“OK”AT- 测试连接。如果回复OK说明通信正常。ATNAMEWaterAssistant- 将蓝牙名称设置为“WaterAssistant”。你可以改成任何你喜欢的名字但避免特殊字符。ATPSWD1234- 将配对密码设置为“1234”。建议使用简单密码方便测试。ATUART38400,0,0- 设置蓝牙通信的波特率为38400停止位1无校验位。这一步至关重要必须与我们将要编写的主程序中的蓝牙通信波特率一致。参数格式为波特率,停止位,校验位0代表1位停止位和无校验。ATRESET- 重启蓝牙模块使所有设置生效。发送后模块会重启LED恢复快闪表示已退出AT模式进入正常可配对状态。实操心得如果发送AT后没反应首先检查接线尤其是TX/RX是否交叉连接设备的TX接对方的RX。其次确认HC-05是否成功进入了AT模式LED慢闪。最后尝试在发送的指令后手动加上回车换行这就是为什么串口监视器要选“Both NL CR”。配置完成后断开电路将HC-05模块按2.2节第三步的描述记得加上分压电阻接回你的主项目电路。现在用手机蓝牙搜索应该就能找到名为“WaterAssistant”的设备使用密码“1234”配对。配对成功后HC-05模块上的LED会由快闪变为双闪或常亮因型号而异。4. 固件编程代码逐行解析与逻辑实现主程序负责整个设备的逻辑初始化传感器、校准、读取重量、判断饮水动作、与手机通信、控制提醒。原项目代码提供了一个框架但存在一些错误和不清晰的地方。下面是我重构并详细注释的完整代码。4.1 库引入与全局变量定义// 引入必要的库 #include HX711.h // 用于驱动HX711称重模块 #include SoftwareSerial.h // 用于创建软串口与蓝牙通信避免占用硬件串口 // 引脚定义 #define HX711_DT_PIN A1 // HX711数据引脚接A1 #define HX711_SCK_PIN A2 // HX711时钟引脚接A2 #define BUZZER_PIN 13 // 蜂鸣器控制引脚 // 对象实例化 HX711 scale; // 创建一个HX711传感器对象命名为scale SoftwareSerial bluetooth(10, 11); // 创建一个软串口对象RX10, TX11用于连接HC-05 // 全局变量 float calibration_factor -40500.0; // 校准系数初始为负值根据传感器安装方向调整 float bottle_weight_grams 0.0; // 空杯子的重量皮重单位克 float last_stable_weight 0.0; // 上一次稳定的重量读数用于判断变化 float water_drunk_ml 0.0; // 本次已喝水量毫升 float daily_goal_ml 2000.0; // 每日饮水目标默认2000毫升 unsigned long last_drink_time 0; // 上次喝水的时间戳 const unsigned long REMINDER_INTERVAL 3600000; // 提醒间隔1小时3600000毫秒 const float GRAMS_PER_ML 1.0; // 重量与体积换算系数假设水密度1g/ml代码解读使用SoftwareSerial库在引脚10和11上虚拟一个串口这样我们就可以用bluetooth.println()来发送数据而把硬件串口引脚0,1留出来给电脑调试使用互不干扰。这是比原代码更合理的做法。calibration_factor是核心参数。它不是一个固定值必须通过校准获得。它表示传感器读数与真实重量克之间的比例关系。负号是因为传感器安装方向可能导致读数反向。我们引入了last_stable_weight和water_drunk_ml来更精确地追踪饮水过程而不是简单判断一个阈值。4.2 Setup() 初始化函数void setup() { // 初始化硬件串口用于调试输出连接电脑 Serial.begin(9600); Serial.println(F( 智能饮水助手启动 )); // 初始化蓝牙软串口波特率需与HC-05配置的UART波特率一致38400 bluetooth.begin(38400); bluetooth.println(F(DEVICE READY)); // 向手机发送就绪信号 // 初始化蜂鸣器引脚为输出模式 pinMode(BUZZER_PIN, OUTPUT); digitalWrite(BUZZER_PIN, LOW); // 确保蜂鸣器初始为关闭状态 // 初始化HX711称重传感器 scale.begin(HX711_DT_PIN, HX711_SCK_PIN); scale.set_scale(calibration_factor); // 设置校准系数 scale.tare(); // 执行去皮操作将当前重量设为零点 // 等待传感器稳定 Serial.println(F(请勿在传感器上放置任何物品...)); delay(2000); Serial.println(F(初始化完成。)); // 首次读取获取环境零点漂移的参考值 last_stable_weight scale.get_units(5); // 读取5次取平均提高稳定性 Serial.print(F(当前零点读数: )); Serial.println(last_stable_weight); // 通过串口发送简单操作指南 Serial.println(F(命令列表:)); Serial.println(F( t - 执行去皮将当前重量设为零)); Serial.println(F( c - 开始校准流程)); Serial.println(F( r - 重置今日饮水量)); }关键点解析scale.tare()去皮功能。无论此时传感器上有什么比如空杯子执行后都会将当前重量定义为“0克”。这是使用前必须做的。scale.get_units(5)参数5表示连续读取5次然后取平均值。这能有效滤除一些随机噪声得到更稳定的重量值。这个技巧在传感器应用中非常常用。初始化时向蓝牙发送DEVICE READY可以让手机App在连接后立即知道设备已启动。4.3 核心循环逻辑与重量处理loop()函数以极高的频率不断运行我们需要在其中高效地处理几件事读取重量、判断是否喝水、检查是否需要提醒、处理串口命令。void loop() { // 1. 读取当前重量滤波后 float current_weight scale.get_units(10); // 读取10次取平均进一步平滑数据 // 2. 处理来自硬件串口电脑的命令 if (Serial.available()) { char cmd Serial.read(); handleSerialCommand(cmd, current_weight); } // 3. 处理来自蓝牙手机的命令和数据 if (bluetooth.available()) { String btMsg bluetooth.readStringUntil(\n); // 读取直到换行符 btMsg.trim(); // 去除首尾空格或换行符 handleBluetoothCommand(btMsg, current_weight); } // 4. 核心逻辑判断饮水动作 checkDrinkingAction(current_weight); // 5. 定时提醒逻辑 checkReminder(); // 6. 可选定期向手机发送状态更新 static unsigned long last_report 0; if (millis() - last_report 10000) { // 每10秒报告一次 sendStatusToPhone(current_weight); last_report millis(); } // 短暂延迟避免循环过快 delay(50); }重量读取的稳定性处理 原始代码直接使用scale.get_units()这容易受到瞬时干扰。我采用了两种滤波方法均值滤波get_units(10)本身就是在硬件层面进行10次采样平均。软件阈值滤波在checkDrinkingAction函数中只有当重量变化超过一个“死区”阈值例如5克并保持一段时间才被认为是有效动作而不是手不小心碰了一下杯子。4.4 关键功能函数实现下面拆解几个最重要的子函数。函数1处理串口命令handleSerialCommand这个函数用于通过电脑调试非常实用。void handleSerialCommand(char cmd, float current_weight) { switch(cmd) { case t: case T: scale.tare(); last_stable_weight 0.0; Serial.println(F( 已去皮。当前重量已归零。)); bluetooth.println(STATUS:TARE_OK); break; case c: case C: startCalibrationProcedure(); break; case r: case R: water_drunk_ml 0.0; Serial.println(F( 今日饮水量已重置。)); break; default: Serial.print(F(当前重量: )); Serial.print(current_weight); Serial.println(F( g)); Serial.print(F(今日已喝: )); Serial.print(water_drunk_ml); Serial.println(F( ml)); } }函数2校准流程startCalibrationProcedure校准是获得准确重量的唯一方法。你需要一个已知重量的标准砝码如500克。void startCalibrationProcedure() { Serial.println(F( 开始校准...)); Serial.println(F(请移走传感器上所有物品然后按任意键继续。)); while(!Serial.available()); // 等待用户按键 Serial.read(); // 清空缓冲区 scale.tare(); // 去皮确保零点准确 delay(1000); Serial.println(F(请将已知重量的标准砝码如500g放在传感器上放稳后按任意键。)); while(!Serial.available()); Serial.read(); long known_weight_grams 500; // 你的标准砝码重量单位克 long raw_reading scale.read_average(20); // 读取20次原始ADC平均值 // 计算校准系数已知重量 / 原始读数 // HX711库的设计是重量 (原始读数 - 零点偏移) / 校准系数 // 因此校准系数 (原始读数 - 零点偏移) / 已知重量 // 由于之前执行了tare()零点偏移已被归零所以 calibration_factor raw_reading / known_weight_grams; scale.set_scale(calibration_factor); // 应用新系数 Serial.print(F( 校准完成新系数: )); Serial.println(calibration_factor); // 可以将这个系数保存到EEPROM下次开机自动读取避免重复校准 }原理剖析校准的本质是建立传感器原始读数一个无单位的数字与实际物理重量克之间的数学关系。calibration_factor就是这个比例系数。通过已知重量的砝码我们可以反算出这个系数。函数3判断饮水动作checkDrinkingAction这是项目的核心算法需要巧妙地区分是正常喝水还是其他干扰。void checkDrinkingAction(float current_weight) { static float drink_start_weight 0.0; static bool drinking false; const float DRINK_THRESHOLD 10.0; // 变化超过10克才认为是开始/结束喝水 const unsigned long DRINK_TIMEOUT 5000; // 超过5秒无变化认为喝水结束 float weight_change last_stable_weight - current_weight; // 检测到重量显著减少可能是开始喝水 if (!drinking weight_change DRINK_THRESHOLD) { drinking true; drink_start_weight last_stable_weight; Serial.println(F(检测到拿起水杯...)); } // 处于喝水状态中且重量趋于稳定变化很小 if (drinking abs(weight_change) 2.0) { static unsigned long stable_start 0; if (stable_start 0) { stable_start millis(); } else if (millis() - stable_start 1000) { // 稳定超过1秒 // 计算喝水量 float water_weight drink_start_weight - current_weight; if (water_weight 5.0) { // 忽略极小的变化 water_drunk_ml water_weight * GRAMS_PER_ML; Serial.print(F(喝水量: )); Serial.print(water_weight); Serial.println(F( g)); Serial.print(F(今日累计: )); Serial.print(water_drunk_ml); Serial.println(F( ml)); // 发送到手机 bluetooth.print(DRINK:); bluetooth.println(water_drunk_ml); last_drink_time millis(); // 更新上次喝水时间 } // 重置状态 drinking false; stable_start 0; last_stable_weight current_weight; // 更新稳定重量基准 } } else if (drinking) { // 如果重量还在变化重置稳定计时器 // (这部分逻辑需要配合一个计时器变量代码已简化) } // 超时处理如果超过5秒重量还在变可能不是喝水而是其他干扰 if (drinking (millis() - last_drink_time DRINK_TIMEOUT)) { drinking false; Serial.println(F(喝水动作超时已重置。)); last_stable_weight current_weight; } }算法精髓这里实现了一个简单的“状态机”。它不会因为重量一减少就立刻记录喝水而是等待重量稳定下来后计算从动作开始到稳定时的总减少量。这能有效避免拿起杯子又放下、或者只是碰了一下杯子导致的误记录。函数4向手机发送状态sendStatusToPhone定义一种简单的通信协议方便手机App解析。void sendStatusToPhone(float current_weight) { // 协议格式TYPE:VALUE bluetooth.print(WEIGHT:); // 当前重量 bluetooth.println(current_weight, 1); // 保留1位小数 bluetooth.print(TOTAL:); // 累计饮水 bluetooth.println(water_drunk_ml, 0); // 整数毫升 bluetooth.print(GOAL:); // 今日目标 bluetooth.println(daily_goal_ml, 0); // 计算并发送进度百分比 int progress (daily_goal_ml 0) ? (int)((water_drunk_ml / daily_goal_ml) * 100) : 0; bluetooth.print(PROGRESS:); bluetooth.println(progress); }5. 手机端应用构思与通信协议原项目提供了一个Kodular开发的App下载链接。这里我提供一种更通用的思路你可以用任何支持蓝牙串口的App如Serial Bluetooth Terminal进行测试或者用MIT App Inventor、Kodular这类图形化工具甚至Flutter、React Native来开发自己的App。5.1 简易通信协议设计为了让Arduino和手机能够互相理解我们需要约定好数据格式。上面代码中已经用到了这里再明确一下设备 - 手机WEIGHT:250.5当前杯子总重量克DRINK:1250累计饮水量更新毫升STATUS:TARE_OK去皮操作成功DEVICE READY设备启动手机 - 设备TARE执行去皮命令GOAL:2500设置每日目标为2500毫升RESET重置今日饮水量在手机App里你需要建立一个蓝牙串口连接然后不断读取来自Arduino的数据行根据冒号前的“关键字”来解析和处理后面的“数值”。5.2 使用现成App进行快速测试在开发自定义App之前强烈建议先用现成的串口调试App验证整个系统在手机应用商店搜索“Serial Bluetooth Terminal”或“蓝牙串口”并安装。打开App配对并连接“WaterAssistant”。在App的发送框输入TARE并发送观察Arduino串口监视器是否打印“ 已去皮”。拿起水杯喝一口水放回观察App接收区是否会收到DRINK:xxx格式的数据。这个测试能帮你快速确认硬件、蓝牙通信、核心逻辑全部工作正常为后续开发扫清障碍。6. 常见问题排查与调试心得即使按照步骤操作也可能会遇到各种问题。这里把我踩过的坑和解决方案汇总一下。6.1 重量读数不稳定或跳变严重症状数值在几十克甚至上百克范围内无规律跳动。可能原因及解决供电干扰这是最常见的原因。避免使用电脑USB口供电进行精密测量尤其是笔记本电脑。改用手机充电器或稳压电源为Arduino供电。确保HX711模块的5V和GND连接牢固。机械结构不稳称重传感器必须只承受垂直方向的力。检查杯子平台是否稳固传感器是否被侧向挤压或悬空。可以用厚实、平整的底座和顶板。软件滤波不足尝试增加get_units()中的采样次数或在代码中加入更复杂的滤波算法如滑动平均滤波、卡尔曼滤波。校准系数错误重新执行严格的校准流程。确保校准砝码准确且放置时没有触碰其他物体。6.2 蓝牙无法连接或连接后断线症状手机搜不到设备或配对后很快断开数据收发不正常。可能原因及解决电压分压电路错误这是硬件上最大的坑。务必确认连接到HC-05 RXD的电压在3.3V左右。用万用表测量一下最保险。电阻值不精确可能导致电压偏高或偏低。波特率不匹配确保Arduino代码中bluetooth.begin(38400)的波特率与用AT命令ATUART38400,0,0设置的波特率完全一致。电源功率不足当所有模块同时工作时特别是蜂鸣器响起瞬间电流较大可能导致Arduino的5V输出被拉低蓝牙模块复位。尝试在Arduino的5V和GND之间并联一个100-470μF的电解电容稳压。软件串口冲突SoftwareSerial库在某些Arduino型号上不稳定特别是在高波特率下。可以尝试换用AltSoftSerial库更稳定但占用特定引脚或者如果硬件串口空闲直接将HC-05的TXD/RXD连接到Arduino的RX1/TX1如果板子有的话。6.3 喝水量记录不准确症状没喝水也记录或者喝了一大口记录量却很少。可能原因及解决阈值设置不当检查代码中的DRINK_THRESHOLD开始检测阈值和判断稳定的阈值。如果环境振动大需要适当调高DRINK_THRESHOLD比如到15或20克。如果水杯本身很轻喝水动作慢可能需要调低。算法逻辑问题checkDrinkingAction函数中的状态机逻辑需要仔细调试。可以通过串口打印出drinking、weight_change、current_weight等变量的实时值观察在一次真实的喝水动作中这些值是如何变化的从而调整超时时间DRINK_TIMEOUT和稳定判断时间。去皮不准确每次放上空杯子后必须通过发送‘t’命令或手机App触发tare()操作将空杯重量归零。杯子本身的重量必须被扣除。6.4 项目优化与扩展方向这个基础版本已经可以工作但还有很大的优化和扩展空间数据持久化使用Arduino的EEPROM存储calibration_factor、daily_goal_ml甚至每日饮水记录断电后不会丢失。低功耗设计如果想让设备电池供电可以考虑使用Arduino Pro Mini等低功耗型号并让设备大部分时间处于睡眠模式仅定时唤醒检查重量或通过蓝牙中断唤醒。无线升级OTA通过蓝牙接收新的固件程序并自我更新无需再用数据线连接电脑。多用户支持在App端切换用户Arduino根据不同的杯子重量通过皮重区分为不同用户记录数据。云端同步手机App将饮水数据上传到云端服务器生成长期统计图表并支持多设备同步。这个项目从硬件连接到软件逻辑完整地走通了一个物联网感知设备的开发流程。它涉及到的传感器数据采集、滤波处理、状态机逻辑、无线通信和简单协议设计都是嵌入式开发中非常核心的概念。希望这份超详细的拆解能帮你不仅做出这个饮水助手更能理解背后每一步的缘由从而具备独立设计和调试更多有趣项目的能力。