公司动态

基于ATtiny85的智能刷牙计时器:从传感器到低功耗设计的完整实现

📅 2026/8/19 2:48:16
基于ATtiny85的智能刷牙计时器:从传感器到低功耗设计的完整实现
1. 项目缘起一个被忽视的“两分钟”难题你有没有认真计算过自己每天刷牙的时间我猜绝大多数人的答案都是“差不多两分钟吧”。但如果你真的用手机秒表掐一下结果可能会让你大吃一惊。我自己就做过这个实验在没有任何计时工具辅助的情况下凭感觉刷完牙结果平均时间只有45秒左右远低于牙医普遍推荐的2分钟。这个看似微不足道的时间差长期累积下来对口腔健康的潜在影响是巨大的。牙菌斑的清除效率、氟化物的作用时间都与刷牙时长直接相关。刷不够时间就等于给蛀牙和牙龈炎留下了可乘之机。这就是“#TBT (Tooth Brush Timer)”这个项目诞生的最直接原因。它不是一个复杂的智能硬件也不是一个需要联网的App它的核心目标极其单纯且聚焦帮助用户尤其是儿童和容易分心的成年人直观、无感地完成每次至少两分钟的有效刷牙。你可能觉得手机上的计时器或者智能电动牙刷的30秒分区提醒已经足够了但前者需要你特意去操作手机并且可能因此分心刷起社交媒体后者则依赖于价格不菲的特定设备。#TBT 想做的是创造一个独立、低成本、零学习门槛的物理计时解决方案让它像牙刷杯一样自然地融入你的洗漱台成为刷牙这个日常仪式中不可或缺的一部分。2. 核心设计哲学减法艺术与行为引导在设计 #TBT 之初我反复问自己一个问题一个成功的刷牙计时器最重要的特质是什么是精准到毫秒吗是五彩斑斓的灯光秀吗是多达十几种的刷牙模式吗经过与多位朋友包括有孩子的家长的讨论我得出的结论是极致的简单和无需思考的交互。任何增加操作步骤或认知负担的设计在这个场景下都是失败的。用户站在洗手池前睡眼惺忪或匆忙赶时间他们需要的不是另一个需要“使用”的电子产品而是一个能“自动工作”的伙伴。因此#TBT 的设计遵循了严格的“减法”原则零按钮交互整个设备没有物理按键。计时如何开始答案是——通过拿起牙刷这个自然动作。我在牙刷架上集成了一个微型压力传感器或电容感应区域。当你取下牙刷计时自动开始。这个设计巧妙地将“开始计时”这个额外任务无缝嵌入到“准备刷牙”这个固有流程中实现了真正的无感启动。纯粹的视觉反馈摒弃复杂的屏幕或语音提示在安静的清晨或深夜语音可能是一种打扰。#TBT 通过一条环绕设备的LED灯带来显示时间进度。灯带会以某种模式如色彩流动或分段熄灭来直观表示2分钟的流逝。当时间到达时所有灯光会以一个温和但明确的信号如常亮绿色或缓慢闪烁提示结束。这种反馈安静、直观且不会打断用户的刷牙动作。脱离手机它完全独立工作无需蓝牙配对无需下载App无需担心手机没电或更新。它的唯一使命就是计时这反而构成了它最大的可靠性。这种设计哲学的本质是“行为引导”。它不试图教育用户也不提供复杂的数据分析只是通过最轻量的方式修正一个普遍存在的行为偏差——刷牙时间不足。对于儿童来说这种像游戏进度条一样的灯光反馈能极大增加刷牙的趣味性和完成度对于成人它则是一个沉默而可靠的监督者。3. 硬件架构选型与实现细节要实现上述设计硬件的选型需要在成本、功耗、体积和可靠性之间找到最佳平衡点。以下是我在原型开发中做出的核心决策及其背后的考量。3.1 “大脑”的选择为什么是ATTiny对于这样一个功能单一的项目使用Arduino Uno甚至ESP32都显得大材小用且不利于设备小型化和降低功耗。我的选择是ATtiny85一款来自Microchip原Atmel的8位AVR微控制器。理由如下足够用的性能控制LED灯带、读取一个传感器状态、运行一个简单的计时器逻辑ATtiny85的8MHz主频和8KB Flash内存绰绰有余。极低的功耗在活跃模式下功耗仅为几毫安并且非常容易进入深度睡眠模式。我们可以设计成在检测到牙刷放回、计时结束后单片机立即进入睡眠仅保留传感器中断唤醒功能此时整体功耗可降至微安级使用纽扣电池供电也能持续数月。极小的体积SOP-8或DIP-8封装比指甲盖还小可以轻松嵌入各种造型的壳体内部。开发便捷可以利用Arduino IDE进行开发通过USBAsp或Arduino-as-ISP进行烧录生态友好。当然它的局限性也很明显IO口很少仅6个内存和算力有限。但这恰恰迫使我们的设计必须保持精简符合项目初衷。3.2 “感知”的实现传感器方案对比检测“牙刷被取下”这个动作有几种常见方案微动开关机械结构成本最低可靠性高。但需要用户施加一个明确的压力来触发手感可能略显生硬且机械部件有寿命问题。压力传感器FSR可以感知更细微的压力变化能区分“触摸”和“拿起”实现更柔和的交互。但成本稍高且需要模拟信号读取和阈值校准。电容触摸传感器通过在牙刷架区域制作一个触摸电极检测人体或牙刷如果牙刷柄是导电材质接触时的电容变化。这种方式非常灵敏无需物理压力体验最好。ATtiny85本身支持电容触摸检测通过库实现但电路设计和抗干扰处理需要一些技巧。在我的最终原型中我选择了电容触摸方案。因为它提供了最无缝的体验手指轻轻接触牙刷架准备取牙刷时计时就已悄然开始仿佛设备有生命一般。为了实现稳定的电容感应我使用了ADCTouch库它利用ATtiny85的内部ADC来测量电极的充电时间从而检测电容变化无需外部专用芯片。关键电路提示电容触摸电极可以用一块覆铜板或甚至一根导线盘绕制成连接到ATtiny85的一个ADC引脚。电极与单片机引脚之间建议串联一个1MΩ的电阻作为基本的过流保护。整个感应区域需要做好绝缘例如覆盖一层薄薄的亚克力或塑料贴纸并与其它电路保持一定距离以减少干扰。3.3 “表达”的方式LED灯带驱动视觉反馈是#TBT的灵魂。我选择了WS2812B智能LED灯带俗称NeoPixel。它只需要单片机的一个数字IO引脚就能通过单线归零码协议控制上百颗LED每颗LED都可以独立设置RGB颜色实现丰富的动态效果。为什么不用更简单的普通LED动态效果需求要直观展示时间流逝例如让灯光像进度条一样逐渐熄灭或者色彩从暖色调向冷色调平滑过渡普通LED需要大量IO口和复杂的PWM控制而WS2812B一颗就能搞定一串。节省IO口对于IO紧张的ATtiny85这一点至关重要。一颗WS2812B灯珠就是一个像素点我们可以只用几颗甚至一颗通过颜色变化来表达进度这比用多个单色LED节省了大量引脚。编程友好有成熟的Adafruit_NeoPixel库支持控制逻辑非常简洁。连接与供电注意WS2812B对电源质量比较敏感。必须确保电源无论是3.7V锂电升压到5V还是直接使用5V电源能提供足够的电流每颗LED全白亮时约60mA。在单片机IO引脚与灯带数据线之间务必串联一个300-500Ω的电阻以抑制信号振铃提高稳定性。数据线末端最好接一个100-220pF的电容到地用于滤波。3.4 能源管理续航的艺术作为常驻洗漱台的小设备我不希望它拖着一条电线。无线化、长续航是提升体验的关键。我选择了**一节普通的CR2032纽扣电池3V**作为电源。但这带来了挑战ATtiny85和WS2812B的工作电压都是5V。升压方案我选用了一颗微型、高效的DC-DC升压芯片如MT3608可以将3V升压至稳定的5V。选择芯片时需特别关注其在低负载下的效率因为设备大部分时间处于睡眠状态静态电流要极小。功耗控制实战核心策略——睡眠模式在loop()函数中完成一次灯光更新和传感器检测后如果计时未开始或已结束立即让ATtiny85进入SLEEP_MODE_PWR_DOWN深度睡眠。此模式下只有外部中断和看门狗定时器如果启用可以唤醒它功耗低于1微安。中断唤醒将电容触摸传感器对应的引脚配置为外部中断引脚PCINT。当手指触摸导致电容变化引脚电平波动会触发中断将单片机从深度睡眠中唤醒开始计时流程。这是实现“零待机功耗”的关键。灯光亮度管理WS2812B在显示时是主要的耗电单元。在代码中将LED的亮度通过setBrightness()设置调至一个在室内环境下清晰可见的最低值例如30-50能显著降低电流消耗。关闭未用模块在软件中禁用ADC、定时器等未使用的外设模块。通过以上组合策略我的原型在每天使用两次、每次亮灯2分钟的情况下一节CR2032电池可以稳定工作超过3个月。这个续航水平对于免维护设备来说是完全可以接受的。4. 软件逻辑与代码核心剖析硬件是骨架软件才是灵魂。#TBT的固件逻辑清晰但精妙核心是状态机管理和低功耗设计。4.1 程序状态机设计设备的行为可以用三个状态来概括SLEEP睡眠默认状态。单片机深度睡眠仅电容触摸中断有效。LED熄灭。TIMING计时中牙刷被取下2分钟计时开始。LED灯带以预设模式显示剩余时间。COMPLETE完成2分钟到。LED给出完成提示如常亮绿色5秒。之后若无操作自动返回SLEEP状态。状态之间的转换由事件触发触摸中断- 从 SLEEP 进入 TIMING。2分钟定时器到- 从 TIMING 进入 COMPLETE。COMPLETE状态持续5秒或计时中再次检测到触摸模拟提前结束/重置- 返回 SLEEP。4.2 关键代码片段与解析以下是基于Arduino核心、在ATtiny85上运行的核心逻辑框架。注意为了适配ATtiny85我们需要使用特定的引脚定义和库。#include avr/sleep.h #include avr/power.h #include Adafruit_NeoPixel.h // 注意需要安装支持ATTiny的NeoPixel库或使用“TinyNeoPixel” // 引脚定义 #define NEOPIXEL_PIN 1 // PB1 (对应Arduino引脚1) #define TOUCH_SENSOR_PIN A1 // PB2 (ADC1)用作电容触摸输入 // 参数定义 const unsigned long BRUSH_TIME_MS 120000; // 2分钟 const unsigned long COMPLETE_HOLD_MS 5000; // 完成提示持续5秒 const int NUM_LEDS 8; // LED数量根据实际灯珠数修改 Adafruit_NeoPixel strip Adafruit_NeoPixel(NUM_LEDS, NEOPIXEL_PIN, NEO_GRB NEO_KHZ800); enum DeviceState { SLEEP, TIMING, COMPLETE }; DeviceState currentState SLEEP; unsigned long stateStartTime 0; bool touchDetected false; // 电容触摸读取函数简化版实际需校准和滤波 int readTouchSensor() { // 使用ADCTouch库或手动实现电容测量 // 此处为示意返回模拟值 return analogRead(TOUCH_SENSOR_PIN); } // 外部中断服务程序用于电容触摸唤醒 EMPTY_INTERRUPT(PCINT0_vect); // 空中断仅用于唤醒 // 具体的中断引脚配置需根据实际连接调整 void enterSleep() { set_sleep_mode(SLEEP_MODE_PWR_DOWN); sleep_enable(); // 配置触摸传感器引脚为PCINT中断... sleep_mode(); // 进入睡眠 // 唤醒后继续执行 sleep_disable(); } void updateLEDForTiming(unsigned long elapsed, unsigned long total) { float progress (float)elapsed / total; int ledsToLight NUM_LEDS - (int)(progress * NUM_LEDS); // 倒计时点亮的LED数减少 strip.clear(); for(int i0; iledsToLight; i) { // 可以设置颜色渐变例如从蓝色开始到红色结束 int r map(i, 0, NUM_LEDS-1, 0, 255); int b map(i, 0, NUM_LEDS-1, 255, 0); strip.setPixelColor(i, strip.Color(r, 0, b)); } strip.show(); } void showComplete() { strip.clear(); for(int i0; iNUM_LEDS; i) { strip.setPixelColor(i, strip.Color(0, 255, 0)); // 全部绿色 } strip.show(); } void setup() { // 初始化NeoPixel strip.begin(); strip.setBrightness(30); // 设置较低亮度以省电 strip.show(); // 配置触摸传感器引脚和中断... // 禁用所有不必要的外设以省电 power_adc_disable(); // ... 其他power_*_disable(); // 初始状态 currentState SLEEP; } void loop() { switch(currentState) { case SLEEP: enterSleep(); // 被中断唤醒后检测是否是有效触摸 if(/* 有效的触摸信号 */) { currentState TIMING; stateStartTime millis(); } break; case TIMING: { unsigned long now millis(); unsigned long elapsed now - stateStartTime; if(elapsed BRUSH_TIME_MS) { currentState COMPLETE; stateStartTime now; showComplete(); } else { updateLEDForTiming(elapsed, BRUSH_TIME_MS); // 可以在此处加入再次触摸检测用于手动提前结束 } } // 短暂延迟降低更新频率以省电例如每100ms更新一次LED delay(100); break; case COMPLETE: if(millis() - stateStartTime COMPLETE_HOLD_MS) { strip.clear(); strip.show(); currentState SLEEP; } // 完成状态也可以检测触摸立即返回睡眠 if(/* 检测到触摸 */) { strip.clear(); strip.show(); currentState SLEEP; } delay(100); break; } }代码要点解析低功耗核心enterSleep()函数是关键。它设置了最深的睡眠模式并通过配置好的外部中断电容触摸变化作为唤醒源。防溢出处理在实际代码中millis()的溢出约50天后需要处理。可以使用unsigned long差值比较的技巧或者使用定时器中断来维护一个软件计时器。触摸去抖在中断唤醒后不能立即判定为有效触摸需要一小段延迟后再次读取传感器值进行软件去抖和阈值判断防止误触发。亮度与更新率权衡LED亮度设低更新频率也降低delay(100)都是为了在TIMING状态下也能尽可能省电。5. 外壳设计与用户体验打磨硬件和软件跑通只成功了三分之一。让#TBT成为一个“产品”而非“实验板”外壳设计和用户体验细节至关重要。5.1 造型与集成思路我的设计目标是让它看起来不像一个电子设备而是洗漱台的自然组成部分。有几个方向牙刷架集成式直接3D打印或CNC加工一个带有放置凹槽的牙刷架将电路和灯带嵌入其中。这是最一体化的方案用户无需改变任何习惯。独立贴附式设计一个扁平、圆润的小盒子背面有强力胶或吸盘可以贴在镜子旁边或墙上。灯带作为装饰环露出。这种方式灵活性高适合已经有好用牙刷架的家庭。趣味化造型针对儿童可以设计成小动物、卡通人物的造型让灯带成为它的眼睛或光环增加亲和力。我选择了第一种方案进行原型制作。使用Fusion 360进行建模设计了一个中间有圆柱形空腔放置牙刷的底座空腔壁内预埋了电容感应铜箔。底座边缘有一圈凹槽用于嵌入WS2812B灯带。整个外壳由上盖和下盖组成方便组装和更换电池。材料选择我使用了白色PETG材料进行3D打印。PETG强度足够表面质感较好且耐潮湿非常适合浴室环境。打印完成后用细砂纸打磨并喷涂一层哑光清漆进一步提升质感并防水。5.2 防水防潮处理浴室环境高温高湿是电子设备的“杀手”。必须做好密封电路板防护给整个控制板ATtiny85、升压模块等喷涂三防漆。这是一种特殊的绝缘涂层能有效防潮、防腐蚀、防尘。外壳密封上下盖的结合处设计一圈凹槽嵌入硅胶密封圈。固定螺丝的孔位内侧也加上硅胶垫片。开关与充电口由于我们设计为无开关、无线充电换电池所以不存在这些额外的进水点简化了密封难度。电池仓盖同样需要硅胶圈密封。LED灯带WS2812B灯珠本身有一定防水性但为了长久使用我在嵌入灯带后在凹槽内填充了透明的中性硅酮密封胶既固定了灯带又提供了防水屏障同时不影响光线透出。5.3 灯光效果的人因工程学灯光不仅仅是显示时间更是与用户沟通的桥梁。我设计了两种主要的灯光模式并进行了小范围的用户测试进度衰减模式初始时所有LED点亮如蓝色。随着时间流逝LED从一端开始依次熄灭就像一根燃烧的“导火索”或减少的“进度条”。直观地显示了剩余时间比例。色彩流动模式LED的颜色从冷色调蓝随时间平滑过渡到暖色调红。2分钟结束时全部变为红色并闪烁。这种模式更具美感但“剩余时间”的直观性稍弱。测试发现对于儿童“进度衰减模式”的理解成本更低效果更好。对于成人两种模式均可接受。因此我在最终版本中通过一个隐藏的磁铁开关在特定位置放置磁铁几秒来切换这两种模式满足不同偏好。完成提示2分钟结束时所有LED快速闪烁绿色三次然后保持柔和绿色常亮5秒。这个信号需要明确区别于计时中的状态但又不能过于刺眼避免清晨刺激。绿色的选择也传达了“完成、良好”的积极暗示。6. 原型测试、迭代与真实场景挑战将第一个粗糙的原型放在自家洗手间使用是发现问题最快的方式。我记录了长达一个月的测试日志遇到了几个意料之外但至关重要的问题。6.1 误触发与抗干扰问题最初版本在无人使用时偶尔会自己亮起来误触发计时。原因是电容传感器过于敏感不仅对手指对空气湿度骤变、附近移动的物体如毛巾产生的静电也可能产生反应。解决方案软件滤波将单次触发改为“持续检测”。在中断唤醒后不是立即进入TIMING状态而是启动一个50毫秒的检测窗口在此期间连续多次读取传感器值只有超过阈值一定次数才判定为有效触摸。这能过滤掉瞬时的干扰脉冲。阈值动态校准设备在每次上电或长时间无触发后自动进行一次环境基准值采样。后续的触摸判断基于这个动态基准的偏移量而不是固定阈值从而适应不同的环境湿度和温度。物理屏蔽在电容感应电极周围用接地的铜箔制作一个“屏蔽环”将感应区域限制在牙刷放置槽内减少外部干扰。6.2 电池仓的“反人类”设计问题第一版外壳的电池仓需要用小螺丝刀拧开两颗非常细的螺丝才能更换CR2032电池。这个过程极其繁琐严重影响了用户体验。迭代我重新设计了电池仓结构改为滑动式卡扣盖板。用力一推即可打开更换电池后扣上即可完全无需工具。这个改动虽然增加了模具或3D打印结构的复杂度但对用户体验的提升是巨大的。一个小教训在设计初期就必须把“维护”作为最重要的使用场景之一来考虑。6.3 不同牙刷的兼容性问题我家里的电动牙刷手柄较粗而普通牙刷较细。固定的牙刷槽要么太紧要么太松影响电容感应效果和放置稳定性。解决方案放弃“紧密贴合”的思路将牙刷槽设计得略大于最大牙刷手柄并在槽内顶部增加一个硅胶软垫。无论牙刷粗细放入后都会轻微挤压硅胶垫确保刷柄与槽壁内嵌感应电极有稳定且足够的接触面积同时硅胶也起到了防滑、减震的作用。6.4 视觉反馈在强光下的可见度问题白天浴室光线很强时LED灯带的亮度显得不足难以看清。解决方案硬件层面选择亮度更高的LED灯珠型号并在允许的功耗范围内在代码中根据环境光可增加一个光敏电阻但会增加复杂度或固定时间段如设定白天模式适当提高亮度。设计层面在灯带上方使用磨砂半透明的导光条来扩散光线。这样即使单个LED点看起来不那么亮但整个光带是均匀柔和的在强光下反而因为发光面积大而更容易被察觉。磨砂表面也提升了产品的质感。经过几轮这样的“发现问题-分析原因-迭代解决”#TBT从一个脆弱的实验室原型逐渐变成了一个可靠、耐用、体验流畅的日常用品。这个过程让我深刻体会到硬件产品开发中软件逻辑的正确只是起点与物理世界和人交互的细节才是决定成败的关键。