公司动态

基于ATTiny85的智能刷牙计时器:从硬件选型到低功耗设计的完整实践

📅 2026/8/19 2:50:16
基于ATTiny85的智能刷牙计时器:从硬件选型到低功耗设计的完整实践
1. 项目概述一个被低估的日常效率工具每天早上和晚上我们都会拿起牙刷开始一天中看似最微不足道却又必不可少的仪式。但有多少人能自信地说自己每次刷牙都刷够了牙医推荐的两分钟我猜大多数人都是凭感觉刷个几十秒就草草了事。我自己以前也是这样直到一次洗牙时牙医指着牙菌斑显示剂染色的区域对我说“这里、这里还有这里都是你平时没刷到位的地方时间肯定不够。”那一刻我才意识到一个简单的“两分钟”原则执行起来竟如此困难。于是我萌生了一个想法为什么不做一个专属于刷牙的计时器不是手机里那些功能繁杂、广告横行的App也不是厨房里滴答作响的机械计时器而是一个极简、专注、甚至能带来一点仪式感的物理设备。这就是#TBT (Tooth Brush Timer)项目的由来。它本质上是一个软硬件结合的小型计时器核心目标只有一个确保你每次刷牙都刷满两分钟并通过简单的交互和反馈帮助你养成更科学的刷牙习惯。这个项目适合所有关注个人健康、有轻微“技术控”倾向、并乐于动手改善生活细节的朋友。它不要求你有深厚的电子工程背景但需要你对手工制作、基础电路和编程有基本的兴趣和耐心。通过完成它你不仅能收获一个实用的健康小工具更能深入理解如何将一个日常痛点转化为一个可触摸、可交互的解决方案的全过程。2. 项目整体设计与思路拆解2.1 核心需求与功能定义在动手之前我们必须明确 #TBT 要解决的核心问题是什么以及它应该具备哪些功能。这决定了我们后续的硬件选型和软件逻辑。首先核心痛点是“刷牙时间不足且无法量化”。因此最核心的功能就是精准的两分钟倒计时。但这还不够一个好的用户体验需要考虑整个使用流程启动便捷开始刷牙时如何一键启动计时最好能模拟“挤牙膏”或“拿起牙刷”这个动作。过程反馈刷牙的两分钟里用户需要知道进度。是无声地等待还是需要有阶段性的提示比如每30秒提醒换一个区域刷。结束提醒时间到必须以明确无误的方式告知用户。是蜂鸣器响还是灯光变化省电与待机这是一个每天只用两次的设备绝大部分时间处于闲置状态必须极低功耗。基于以上我定义了 #TBT 的MVP最小可行产品功能清单输入一个物理按钮用于启动计时。输出视觉反馈一个多色LED如RGB LED用于显示状态如待机、计时中、结束。听觉反馈一个蜂鸣器或微型扬声器用于播放阶段提示音和结束提示音。核心逻辑微控制器执行两分钟倒计时并在第30秒、60秒、90秒、120秒结束时触发相应的灯光和声音提示将口腔分为四个象限进行分区计时。供电使用纽扣电池或小型锂电池确保数周甚至数月的待机时间。2.2 硬件方案选型与考量硬件是项目的骨架。我的选型原则是在满足功能的前提下尽可能选择常见、易得、易焊接的元件并优先考虑低功耗。1. 微控制器大脑这是最关键的决策。常见的选择有 Arduino Uno、ESP8266/ESP32、ATTiny85 等。Arduino Uno功能强大但体积大、功耗高不适合做小型便携设备。否决。ESP系列自带Wi-Fi/蓝牙性能强但功耗相对较高且对于纯计时任务来说杀鸡用牛刀。否决。ATTiny85AVR架构的8位微控制器仅有8个引脚体积小巧功耗极低睡眠模式下微安级且可以通过Arduino IDE进行编程生态友好。它是本项目的最佳选择。其有限的IO口5个可用刚好满足我们的需求1个按钮输入1个LED输出1个蜂鸣器输出还剩2个备用。2. 电源管理为了便携和长待机我选择了CR2032 纽扣电池3V。其优点是电压合适、体积小、易更换。但需要注意ATTiny85的工作电压范围是1.8-5.5V3V供电完全没问题但直接驱动LED和蜂鸣器可能会因为电压不足而亮度/响度不够。因此我们需要为LED和蜂鸣器设计独立的驱动电路或者选择低压驱动的元件。3. 反馈元件LED选用一个共阳极RGB LED。为什么是RGB因为我们可以用不同颜色表示不同状态红色常亮表示电量低蓝色呼吸表示待机绿色闪烁表示计时开始然后依次切换颜色如蓝-绿-黄-红来指示四个30秒区间最后快速闪烁表示结束。一个元件实现多种状态节省IO口。蜂鸣器选用无源蜂鸣器。有源蜂鸣器通电就响只能发一种声音无源蜂鸣器需要不同频率的方波驱动可以演奏简单的旋律可玩性更高。我们可以为“阶段提示”和“结束提示”设计不同的短促音效。4. 交互元件一个6x6mm 轻触开关足矣。考虑防水需求毕竟在卫生间使用可以选用带硅胶帽的密封按键或者在成品外壳上做好防水处理。5. 电路设计思路整体电路非常简单。ATTiny85为核心按钮接一个IO口并启用内部上拉电阻。RGB LED的三个阴极通过三个限流电阻约220欧姆分别接到三个IO口阳极接VCC3V。无源蜂鸣器一端接另一个IO口另一端接地。这里有一个关键点为了在3V电压下让蜂鸣器足够响可以考虑用一个三极管如S8050进行放大驱动。注意在纽扣电池供电系统中所有元件的静态功耗都必须仔细考量。ATTiny85要设置为在待机时进入深度睡眠模式仅靠按钮中断唤醒。RGB LED在待机时应完全熄灭不能有任何漏电流。2.3 软件逻辑与状态机设计软件是项目的灵魂。我们需要用一个清晰的状态机来管理设备的所有行为。设备主要有以下几个状态深度睡眠状态主循环停止仅保留外部中断唤醒功能。RGB LED熄灭蜂鸣器静音。功耗极低。待机唤醒状态按下按钮后芯片被唤醒进入待机模式。RGB LED呈现缓慢呼吸灯效果比如蓝色表示设备已就绪等待启动计时。刷牙计时状态在待机状态下再次按下按钮进入此状态。这是一个持续120秒的倒计时子状态机。它内部又分为4个30秒的子阶段。每个子阶段LED显示一种颜色并在阶段切换时每30秒让蜂鸣器发出一个短促提示音如“嘀”一声提示用户更换刷牙区域。计时完成状态120秒倒计时结束。LED快速闪烁比如红绿交替蜂鸣器播放一段完成的旋律如“嘀嘀嘀-嘀”。持续5-10秒后自动跳转回深度睡眠状态。此外还需要一个低电量检测状态。可以利用ATTiny85的内部基准电压和ADC功能定期比如每次唤醒时检测电池电压。当电压低于阈值如2.7V时无论处于何种状态都让LED显示红色常亮提醒更换电池。3. 核心细节解析与实操要点3.1 ATTiny85的深度睡眠与中断唤醒配置实现超长待机的关键在于让MCU在不工作时“睡死”。对于ATTiny85我们可以使用avr/sleep.h库。#include avr/sleep.h #include avr/power.h #include avr/wdt.h // 设置睡眠模式为最省电的 POWER_DOWN set_sleep_mode(SLEEP_MODE_PWR_DOWN); sleep_enable(); // 配置引脚中断假设按钮接在引脚2即PB3 pinMode(buttonPin, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(buttonPin), wakeUp, FALLING); // 下降沿触发 // 进入睡眠 sleep_cpu(); // 唤醒后执行的函数 void wakeUp() { // 首先禁用中断防止误触发 detachInterrupt(digitalPinToInterrupt(buttonPin)); // 其他唤醒初始化代码... }实操要点去抖动机械按钮在按下时会产生一段时间的电平抖动可能误触发多次中断。必须在中断服务程序ISR或主循环中做软件去抖动通常延时20-50毫秒再检测引脚状态。关闭未用模块在进入睡眠前使用power_all_disable()关闭ADC、定时器等所有外设以省电。看门狗定时器如果需要有定时唤醒功能如定时检查电量可以配置看门狗定时器WDT中断唤醒。但本项目完全由用户按钮触发无需使用。3.2 多色LED的状态指示与PWM控制我们用单个RGB LED传达丰富信息这就需要精确控制其亮灭和颜色。由于使用了共阳极LED阳极接VCC我们需要通过将阴极引脚拉低输出LOW来点亮对应颜色的LED。颜色混合要显示黄色需要同时点亮红色和绿色。要显示白色则同时点亮红、绿、蓝。但ATTiny85的引脚输出是数字信号HIGH/LOW要实现呼吸灯效果或调节亮度必须使用PWM脉冲宽度调制。// 定义引脚 const int redPin 0; // PB0 const int greenPin 1; // PB1 const int bluePin 4; // PB4 // 设置引脚为输出 pinMode(redPin, OUTPUT); pinMode(greenPin, OUTPUT); pinMode(bluePin, OUTPUT); // 显示纯绿色仅绿色阴极拉低 digitalWrite(redPin, HIGH); // 关闭红 analogWrite(greenPin, 0); // PWM值0相当于持续拉低最亮 digitalWrite(bluePin, HIGH); // 关闭蓝 // 显示橙色红亮绿半亮 analogWrite(redPin, 50); // 红色中等亮度PWM值小亮度高 analogWrite(greenPin, 150); // 绿色较低亮度 digitalWrite(bluePin, HIGH); // 实现呼吸灯效果以蓝色为例 for (int brightness 0; brightness 255; brightness) { analogWrite(bluePin, 255 - brightness); // 注意共阳极是反相PWM delay(10); } for (int brightness 255; brightness 0; brightness--) { analogWrite(bluePin, 255 - brightness); delay(10); }注意ATTiny85并非所有引脚都支持硬件PWM。通常PB0和PB1支持PB4可能不支持。如果PB4不支持硬件PWM又想用它做呼吸灯就需要用软件模拟这会占用大量CPU时间且效果不佳。因此在PCB布局时务必把需要PWM控制的颜色接到支持硬件PWM的引脚上。3.3 无源蜂鸣器的驱动与简单旋律编程无源蜂鸣器就像一个小型扬声器需要输入不同频率的方波才能发出不同音调。我们可以利用tone()函数来驱动但ATTiny85的核心库可能不直接支持tone()或者该函数可能与中断、睡眠产生冲突。更可靠的方法是使用一个硬件定时器来生成特定频率的PWM信号。一个更简单实用的方法是既然我们的音效很简单只有几个提示音我们可以预先计算好音调对应的频率和延时然后用digitalWrite快速翻转引脚来模拟方波。// 简单发出一个1KHz声音持续200ms void beep(int pin, int frequency, long duration) { long period 1000000L / frequency; // 计算周期微秒 long elapsedTime 0; while (elapsedTime duration * 1000L) { digitalWrite(pin, HIGH); delayMicroseconds(period / 2); digitalWrite(pin, LOW); delayMicroseconds(period / 2); elapsedTime period; } } // 播放“完成”旋律例如中音Do, Re, Mi void playFinishMelody(int pin) { beep(pin, 523, 200); // Do delay(50); beep(pin, 587, 200); // Re delay(50); beep(pin, 659, 300); // Mi }实操心得这种digitalWrite加delayMicroseconds的方式在播放声音时会阻塞整个程序。这意味着在蜂鸣器响的几百毫秒内LED动画会暂停计时也可能不准确。对于 #TBT 这种简单应用短暂的阻塞可以接受。如果要求严格则需要用定时器中断来驱动蜂鸣器但这会大大增加代码复杂度。4. 实操过程与核心环节实现4.1 原型搭建与面包板测试在焊接正式电路板之前必须在面包板上完成功能验证。这是排查逻辑错误和硬件连接问题最安全的阶段。所需材料清单ATTiny85 芯片一枚CR2032 电池座一个共阳极RGB LED 一个220欧姆电阻 三个用于LED限流无源蜂鸣器5V 一个NPN三极管 S8050 一个用于驱动蜂鸣器1k欧姆电阻 一个用于三极管基极限流轻触开关 一个面包板、杜邦线若干接线步骤供电将CR2032电池座的正极接面包板正极轨负极-接负极轨。MCU将ATTiny85插入面包板。其引脚1~RESET接正极通过一个10k上拉电阻会更稳定。引脚8VCC接正极引脚4GND接负极。RGB LED将LED的共阳极最长脚接正极。红色阴极通过220欧电阻接PB0绿色接PB1蓝色接PB4。蜂鸣器驱动蜂鸣器正极接三极管S8050的集电极C蜂鸣器负极接GND。三极管发射极E接GND。基极B通过1k电阻接ATTiny85的某个IO口如PB3。按钮按钮一端接GND另一端接ATTiny85的PB2引脚并在PB2与VCC之间连接一个10k上拉电阻或者启用芯片内部上拉。连接好后使用USBasp或Arduino Uno作为编程器将测试程序烧录进ATTiny85。测试程序应依次验证按钮按下唤醒、LED颜色变化、蜂鸣器发声、低功耗睡眠。4.2 PCB设计与焊接要点面包板验证无误后就可以设计一个专属的PCB了这能让作品更小巧、稳固和专业。我使用立创EDA进行设计。设计要点布局紧凑围绕ATTiny85布局将按钮、LED、蜂鸣器驱动电路放在芯片周围缩短走线。电源路径清晰电池座输入处放置一个10uF的电解电容进行电源滤波防止瞬间电流导致电压跌落复位。预留调试接口留出ATTiny85的SPI编程接口MOSI, MISO, SCK, RESET的焊盘方便后续烧录和调试。考虑装配电池座、按钮、LED的位置要考虑到最终外壳的开孔。LED最好加上导光柱让光线更柔和分散。焊接顺序与技巧先贴片后直插先焊接电阻、三极管等小的贴片元件再焊接芯片座建议使用IC座便于更换芯片最后焊接电池座、按钮等直插元件。焊接ATTiny85如果直接焊芯片务必使用尖头烙铁控制好温度和时间防止静电和过热损坏。焊完后用放大镜检查有无桥连。测试供电焊接完电源部分后先别焊芯片用万用表测量VCC和GND之间的电压是否为3V左右且无短路。4.3 软件代码框架与核心函数以下是 #TBT 主程序的核心框架体现了之前提到的状态机思想。// 引脚定义 const int buttonPin 3; // PB3 (物理引脚2) const int redPin 0; // PB0 const int greenPin 1; // PB1 const int bluePin 4; // PB4 const int buzzerPin 2; // PB2 (驱动三极管基极) // 状态定义 enum DeviceState { SLEEP_MODE, STANDBY, BRUSHING, FINISHED, LOW_BATTERY }; DeviceState currentState SLEEP_MODE; // 计时相关变量 unsigned long brushStartTime 0; const unsigned long brushDuration 120000; // 2分钟单位毫秒 const int segmentDuration 30000; // 30秒 void setup() { // 初始化引脚 pinMode(buttonPin, INPUT_PULLUP); pinMode(redPin, OUTPUT); pinMode(greenPin, OUTPUT); pinMode(bluePin, OUTPUT); pinMode(buzzerPin, OUTPUT); digitalWrite(buzzerPin, LOW); // 初始状态进入睡眠 enterSleepMode(); } void loop() { // 主循环根据当前状态执行不同任务 switch (currentState) { case STANDBY: handleStandbyState(); break; case BRUSHING: handleBrushingState(); break; case FINISHED: handleFinishedState(); break; case LOW_BATTERY: handleLowBatteryState(); break; // SLEEP_MODE 状态下loop() 不会被执行 default: break; } // 在每个状态处理函数中都会检查是否需要进行状态转换 } // 唤醒中断服务程序 void wakeUp() { // 此函数内尽量只做标志位修改避免耗时操作 detachInterrupt(digitalPinToInterrupt(buttonPin)); currentState STANDBY; } void enterSleepMode() { setColor(0, 0, 0); // 关闭LED digitalWrite(buzzerPin, LOW); // 配置睡眠和中断 set_sleep_mode(SLEEP_MODE_PWR_DOWN); sleep_enable(); attachInterrupt(digitalPinToInterrupt(buttonPin), wakeUp, FALLING); sleep_cpu(); // 程序在此挂起直到中断唤醒 sleep_disable(); // 唤醒后继续执行 } void handleStandbyState() { // 显示呼吸灯效果 breatheLED(0, 0, 255); // 蓝色呼吸 // 检查按钮是否被按下开始刷牙 if (isButtonPressed()) { brushStartTime millis(); currentState BRUSHING; playStartTone(); setColor(0, 255, 0); // 绿色表示第一个区域开始 } // 可以在这里加入检查电池电量的逻辑 } void handleBrushingState() { unsigned long currentTime millis(); unsigned long elapsed currentTime - brushStartTime; if (elapsed brushDuration) { currentState FINISHED; return; } // 判断当前处于哪个30秒区间 int currentSegment elapsed / segmentDuration; // 0, 1, 2, 3 // 根据区间更新LED颜色 switch (currentSegment) { case 0: setColor(0, 255, 0); break; // 绿 case 1: setColor(255, 255, 0); break; // 黄 case 2: setColor(255, 165, 0); break; // 橙 case 3: setColor(255, 0, 0); break; // 红 } // 检查是否刚到新区间每30秒提示一次 static int lastSegment -1; if (currentSegment ! lastSegment) { lastSegment currentSegment; playSegmentTone(); // 播放“嘀”一声提示音 } // 短暂延时防止loop跑太快 delay(100); } // handleFinishedState 和 handleLowBatteryState 函数省略...5. 外壳设计与制作心得电路和代码完成后#TBT 还需要一个“家”。外壳不仅提供保护更是提升用户体验和美观度的关键。设计考量防水防潮卫生间环境潮湿。外壳应尽可能密封。按钮处采用硅胶防水帽LED和蜂鸣器出声孔要小而密或使用防水透声膜。固定方式如何放置在洗手台可以考虑背面贴3M无痕胶或者设计一个支架。我选择设计一个带倾斜角度的底座让它能自然地立在台面上。人机交互按钮要足够大手感清晰。LED光线不能太刺眼最好通过磨砂亚克力或导光材料进行柔化。电池更换设计可拆卸的后盖方便更换CR2032电池。制作方法3D打印这是最灵活的方式。使用FDM打印机如Creality Ender系列打印PLA材质的外壳。设计时要注意壁厚建议1.2-2mm、预留螺丝柱和卡扣。打印完成后可以进行打磨、喷涂上色。亚克力激光切割适合制作结构简单的层叠式外壳。将设计好的面板顶板、底板、中间隔层用激光切割出来再用螺丝或胶水组装。优点是外观精致透光性好。现成盒子改造找一个尺寸合适的防水接线盒在盒子上开孔安装按钮和LED。这是最快但美观度最差的方法。我的选择与心得我采用了3D打印。首先在Fusion 360里建模将PCB、电池座、按钮的3D模型导入根据它们精确设计内部卡槽和支柱。顶部为LED和蜂鸣器开了密集的小孔阵列既保证透光和出声又能一定程度上防止水溅入。按钮孔上方预留了放置硅胶帽的凹槽。打印时使用0.1mm层高以获得更光滑的表面。完成后我用细砂纸打磨并喷了两层白色底漆和一层哑光清漆最终效果非常令人满意。踩坑记录第一次设计时忽略了按钮的行程和复位空间导致外壳装上后按钮被顶死一直处于按下状态。修改模型时必须在按钮顶部和外壳内壁之间留出至少0.5mm的间隙。另外如果使用内部上拉电阻务必在软件中启用INPUT_PULLUP否则飞线焊接外部上拉电阻在狭小空间内非常麻烦。6. 功耗测试与优化实录对于电池供电设备功耗直接决定了用户体验更换电池的频率。我们必须对成品进行严格的功耗测试。测试工具万用表电流档最好是有数据记录功能的万用表或专用的功耗分析仪。测试步骤与结果深度睡眠模式将设备置于睡眠状态测量电流。理想情况下ATTiny85在POWER_DOWN模式下电流可低于1μA。但实际电路中其他元件可能有漏电。我的初版实测约为5μA。经检查是因为RGB LED的限流电阻直接接到了VCC即使IO口输出高电平仍有极微小的漏电流通路。解决方法是在软件中将控制LED的引脚在睡眠前设置为INPUT模式高阻态彻底断开路径。优化后睡眠电流降至0.8μA。待机唤醒状态此时MCU全速运行LED呼吸灯闪烁。实测平均电流约为3-5mA。这个状态持续时间很短等待用户第二次按键功耗可以接受。刷牙计时状态LED常亮蜂鸣器间歇发声。LED电流最大约10-15mA取决于颜色和亮度。这是功耗最高的阶段但每天总计只持续4分钟。完成提示状态LED闪烁蜂鸣器响。电流在5-20mA间波动。电池寿命估算 以CR2032电池典型容量220mAh计算。每天睡眠23小时56分钟电流0.8μA耗能23.93h * 0.0008mA ≈ 0.019 mAh每天待机操作计4.5分钟平均电流按8mA估算耗能0.075h * 8mA ≈ 0.6 mAh日总耗能 ≈ 0.619 mAh理论续航 220 mAh / 0.619 mAh/天 ≈ 355天这个估算表明优化后的一颗CR2032电池可以使用近一年完全满足实用需求。这个优化过程让我深刻体会到低功耗设计的关键在于关注每一个可能漏电的细节尤其是IO口的状态和外围电路的静态电流。7. 常见问题与排查技巧实录在制作和调试 #TBT 的过程中我遇到了不少典型问题。这里将它们整理成表希望能帮你避开这些坑。问题现象可能原因排查方法与解决方案设备完全无反应LED不亮1. 电池没电或装反。2. 电源线断路或虚焊。3. ATTiny85未烧录程序或烧录失败。4. 芯片损坏。1. 用万用表测电池电压确认2.8V。2. 仔细检查VCC和GND走线重新焊接可疑焊点。3. 用编程器尝试重新烧录一个简单的“点灯”测试程序。4. 更换一片新的ATTiny85。按钮按下无反应无法唤醒1. 按钮接线错误或损坏。2. 中断引脚配置错误。3. 睡眠模式设置不正确。4. 软件中去抖动逻辑过于严格或错误。1. 用万用表通断档检查按钮按下时是否导通。2. 确认digitalPinToInterrupt()使用的引脚号正确。3. 检查set_sleep_mode()和sleep_enable()是否被正确调用。4. 简化程序先去掉去抖动逻辑看中断能否触发。LED颜色显示不正常或很暗1. RGB LED共阴/共阳极接错。2. 限流电阻阻值过大。3. PWM输出引脚不对或代码逻辑反了。4. 电池电压不足。1. 确认是共阳LED阳极接VCC。用万用表二极管档测试LED各引脚。2. 对于3V供电220欧电阻亮度足够。可尝试减小到100欧观察。3. 共阳LED是低电平点亮。确认analogWrite(pin, 0)最亮255最暗。4. 换新电池测试。蜂鸣器不响或声音小1. 是无源蜂鸣器但用了tone()函数或者代码未驱动。2. 三极管驱动电路接错C/E极接反。3. 蜂鸣器本身损坏。4. IO口驱动能力不足。1. 确认代码中buzzerPin有输出频率方波。用示波器或另一个蜂鸣器并联测试。2. 检查S8050三极管基极(B)通过电阻接IO集电极(C)接蜂鸣器正极发射极(E)接GND。3. 直接给蜂鸣器两端加3V直流电看是否微响无源蜂鸣器会轻微“嗒”一声。4. 尝试减小基极限流电阻如从1k降到500欧增大基极电流。计时不准时间忽快忽慢1. 使用了delay()函数且被中断或其他阻塞操作干扰。2. 依赖于millis()但发生了溢出约50天后。3. 芯片内部时钟校准不准。1. 确保计时主循环 (handleBrushingState) 中除了简短的delay(100)外没有其他长延时。2. 对于两分钟计时millis()溢出无需考虑。但好的习惯是用(currentTime - startTime) duration的比较方式可防溢出。3. ATTiny85内部8MHz时钟可能有偏差。可通过烧录器的“芯片编程”功能校准内部振荡器。耗电极快几天就没电1. 睡眠模式未成功进入或电流未降下来。2. LED或外围电路在睡眠时仍在耗电。3. 电池质量差或负载瞬间电流大导致电压骤降。1. 用万用表uA档实测睡眠电流应10μA。检查sleep_cpu()前后是否有代码阻止睡眠。2. 将所有未使用的IO口设置为INPUT_PULLUP或OUTPUT并置低。断开LED、蜂鸣器与VCC的直接连接。3. 在电池端并联一个100uF以上的电解电容缓冲蜂鸣器响瞬间的大电流。最后的个人体会制作 #TBT 的过程远不止是焊接一块电路板和写几行代码。它是一次完整的“产品思维”训练从定义问题、设计解决方案到考虑用户体验、功耗、结构最后进行测试和优化。当我把这个不起眼的小盒子粘在浴室镜旁并真的依靠它养成了刷满两分钟的习惯时那种通过自己双手解决一个真实小问题的满足感是无可替代的。它提醒我技术不只是酷炫的算法和复杂的系统更是让日常生活变得更好一点点的朴素力量。如果你也感兴趣不妨动手做一个从点亮第一颗LED开始感受创造的乐趣。