ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于ATmega328P与DS3231的DIY智能手表:从硬件选型到低功耗设计全解析

基于ATmega328P与DS3231的DIY智能手表:从硬件选型到低功耗设计全解析 1. 从零到一iO Watch 的诞生与核心构想几年前我偶然在抽屉里翻出一块闲置的 ATmega328P 芯片就是 Arduino Uno 上那颗经典的心脏。看着它一个念头冒了出来能不能用它做点不一样的东西比如一块真正能戴在手腕上的、可以自己编程的智能手表市面上成品智能手表功能虽多但总感觉少了点“灵魂”——那种从底层硬件到上层逻辑完全由自己掌控的乐趣。于是iO Watch 这个项目就这么开始了。它的核心很简单用最经典的 Arduino 生态ATmega328P Arduino IDE作为大脑搭配一颗高精度的实时时钟芯片RTC来保证时间的精准再通过自己设计 PCB 将所有部件集成在一块适合佩戴的板子上。这不仅仅是一个电子制作更是一次关于微型化、低功耗和软硬件协同的深度实践。如果你也厌倦了“黑盒”设备渴望从原理图到代码完全理解并创造属于自己的可穿戴设备那么这篇记录或许能给你带来一些启发。2. 核心硬件选型为什么是它们硬件是项目的骨架选型直接决定了项目的可行性、复杂度和最终体验。对于一块以“可编程”和“自制”为核心的手表我的选型逻辑围绕几个关键点展开开发友好、低功耗、高集成度以及足够小的物理尺寸。2.1 主控芯片ATmega328P-AU老将的新战场为什么选择 ATmega328P而不是更强大的 ESP32 或者 STM32原因有三。第一极致的开发友好性。Arduino IDE 对它的支持是“祖传”级别的海量的库、教程和社区资源意味着你几乎不会在软件层面被卡住。对于专注于功能实现和创意验证的项目这能节省大量时间。第二功耗可控。在 3.3V 电压、1MHz 时钟频率下ATmega328P 的主动模式电流可以低至 0.5mA 左右而掉电模式Power-down下的电流更是可以降到 1µA 以下。这对于依赖纽扣电池供电的手表至关重要。第三封装与尺寸。我选择了 TQFP-32 封装的 ATmega328P-AU。相比 DIP 封装它的体积小得多更适合高密度的 PCB 布局。虽然焊接难度略有增加但使用热风枪和适量的焊膏对于稍有经验的爱好者来说完全可以驾驭。这里有一个关键细节Arduino Uno 上使用的是 DIP 封装的 ATmega328P-PU其内部默认使用 16MHz 的外部晶振。但在我们的手表项目中为了进一步降低功耗我选择了使用芯片内部的 8MHz RC 振荡器并通过修改熔丝位Fuse Bits将其作为系统时钟源。这样不仅省去了外部晶振所占用的空间和成本还将功耗降低了约 40%。在 Arduino IDE 中你需要选择“Tools” - “Board” - “Arduino Pro or Pro Mini”然后在“Processor”选项中选择“ATmega328P (3.3V, 8MHz)”。这个选项预设了正确的熔丝位配置非常方便。2.2 实时时钟DS3231SN精准是手表的尊严手表的核心功能是显示时间因此一个精准、可靠的 RTC 模块是必不可少的。DS3231 几乎是这个领域的“明星产品”我选择它的原因非常直接。首先精度极高。它集成了温度补偿晶体振荡器TCXO在 -40°C 到 85°C 的温度范围内精度可以达到 ±2ppm换算成每月误差大约只有 1分钟。这对于一块手表来说完全够用甚至优于很多廉价石英表。其次集成度高。DS3231 内部集成了 32.768kHz 的晶振和匹配电容我们无需在外部再添加这些元件进一步简化了 PCB 设计节省了空间。最后功能齐全。它提供秒、分、时、日、月、年信息支持到 2100 年的闰年补偿还带有两个可编程的闹钟和一个可配置的方波输出为未来扩展功能如整点振动提醒留下了可能。我选择了 DS3231SN 这一贴片型号。与常见的 DS3231 模块上面还集成了电池座和 EEPROM不同SN 型号是单独的芯片需要我们自己在 PCB 上为其设计备份电池电路。这带来了更大的设计自由度和更紧凑的布局。备份电池我选用了一颗常见的 CR1220 纽扣电池通过一个 Schottky 二极管如 1N5817与主电源隔离确保在主电源断开时只有 DS3231 从备份电池获取微安级的电流从而长时间保持计时。2.3 电源管理续航的生命线可穿戴设备的电源设计是重中之重。iO Watch 采用单颗 CR2032 纽扣电池标称 3V220mAh供电。整个系统的功耗必须被严格管控。1. 电压转换与稳压ATmega328P 和 DS3231 都可以工作在 3.3V。理论上CR2032 的电压3V可以直接使用。但在电池电量下降时电压会跌落可能导致单片机工作不稳定。因此我增加了一颗超低静态电流的 LDO低压差线性稳压器例如 MCP1700-3302E。它在 3.3V 输出时静态电流典型值仅为 1.6µA满载时压差仅 178mV非常适合本应用。它确保了即使电池电压降到 2.8V系统仍能获得稳定的 3.3V 供电。2. 电源路径设计整个系统的电源拓扑如下CR2032 正极 - LDO 输入 - LDO 输出3.3V主电源。主电源VCC同时供给 ATmega328P、DS3231 以及其他外围器件。DS3231 的 VBAT 引脚通过一个 Schottky 二极管连接到 CR2032 的正极在 LDO 之前。这样当主电源VCC被切断或电池耗尽时CR2032 仍能通过二极管单独为 DS3231 供电保持计时。3. 功耗控制策略软件层面的功耗控制是续航的关键。ATmega328P 有多种睡眠模式最省电的是掉电模式Power-down。在此模式下只有外部中断、看门狗中断如果使能和 TWI 地址匹配中断用于 I2C可以唤醒它。我们的工作流程设计为大部分时间MCU 处于掉电模式DS3231 的 SQW/INT 引脚被配置为每秒输出一次方波并将此引脚连接到 ATmega328P 的外部中断引脚如 INT0。这样每秒一次的中断将 MCU 从深度睡眠中唤醒MCU 醒来后通过 I2C 从 DS3231 读取当前时间更新显示处理可能的用户输入如按钮然后再次进入睡眠。一次“唤醒-读时-刷新-睡眠”的循环MCU 活跃时间可能只有几毫秒平均电流可以控制在 20µA 左右。按此计算一颗 CR2032 的理论续航可达数月。3. PCB 设计与布局在方寸之间舞蹈将原理图转化为可以生产的 PCB是整个项目从概念走向实体的关键一步也是最考验耐心和细心的环节。我使用的是 KiCad一款强大且开源免费的电子设计自动化工具。3.1 从原理图到 PCB 的完整流程在 KiCad 中工作流非常清晰。首先在“原理图编辑器”中绘制电路图并为每个元件分配正确的符号Symbol和封装Footprint。对于 ATmega328P-AU 和 DS3231SN都需要从库中查找或自己创建对应的封装。绘制完成后使用“注释元件”功能为所有元件分配唯一的标识符如 R1 C2 U1然后运行“电气规则检查ERC”以确保没有诸如未连接的引脚、电源短路等基础错误。ERC 通过后点击“使用 PCB 编辑器更新 PCB”按钮原理图中的元件和网络表Netlist就会被导入到 PCB 编辑器中。这时你会看到所有元件堆叠在画板的一侧无数飞线Ratnest显示了它们之间的连接关系。我们的任务就是将这些元件合理地摆放布局并用铜走线布线替代那些飞线。3.2 布局的艺术功能分区与信号流对于手表这种空间极其受限的 PCB布局优先考虑功能分区和信号流的简洁性。1. 核心功能区我将 PCB 中心区域留给 ATmega328P 和 DS3231。这两个是 I2C 通信的主从设备需要紧挨着放置以缩短 SDA 和 SCL 走线的长度减少信号完整性问题。在它们旁边放置了 I2C 总线的上拉电阻通常 4.7kΩ 或 10kΩ以及去耦电容。ATmega328P 的每个电源引脚VCC 和 AVCC附近都必须放置一个 0.1µF 的陶瓷电容并尽可能靠近引脚放置这是稳定供电、滤除高频噪声的黄金法则。2. 电源输入与输出区LDO 稳压器、输入输出电容、电池连接器或焊盘应放置在一起构成电源模块。电源路径应尽可能短而粗特别是从电池正极到 LDO 输入以及 LDO 输出到主电源网络VCC的走线。我会使用较宽的走线例如 0.5mm 或更宽来降低直流电阻和压降。3. 用户接口区按钮、振动电机如果有、充电接口等需要与用户交互或与外壳配合的元件应严格按照预先设计的外壳结构图放置在 PCB 的边缘指定位置。按钮的走线可能需要连接到 MCU 的外部中断引脚以实现睡眠唤醒。4. 显示接口区我计划使用一款小尺寸的 OLED 显示屏如 0.96 寸 I2C 接口。其连接器或焊盘应放置在 PCB 顶部并预留出 FPC柔性印刷电路或插接件的空间。同样显示屏的 I2C 上拉电阻需要就近放置。布局是一个反复调整的过程。你需要不断在“紧凑”和“可制造性/可维修性”之间权衡。元件之间要留出足够的间隙以防焊接时短路也为可能的返修留出操作空间。3.3 布线的技巧信号完整性与电流能力布局完成后开始布线。我习惯先布电源线再布关键信号线如时钟、I2C最后是一般信号线。1. 电源线加粗如前所述主电源 VCC 的走线要宽。在 KiCad 中你可以为“电源”网络类Net Class设置更大的线宽规则如 0.5mm。地线GND同样重要我倾向于在底层Bottom Layer使用大面积铺铜作为地平面这能提供极低的阻抗回流路径并起到一定的屏蔽作用。2. 信号线等长与间距对于低速的 I2C 总线本项目通常工作在 100kHz等长要求不高但两条线SDA SCL最好平行走线并保持大致相同的长度以避免相位差过大。走线应避免锐角使用 45 度角或圆弧拐弯。信号线之间、信号线与电源线之间要保持一定间距防止串扰。在 KiCad 的设计规则检查DRC中可以设置最小线宽、最小间距等参数布线时实时检查。3. 过孔的使用由于是双面板过孔是连接顶层和底层走线的桥梁。过孔会引入额外的电感和电阻因此电源和地线的过孔可以多用几个并联。对于高频信号过孔也会带来阻抗不连续但在我们这个低频应用中影响不大。需要注意的是过孔的内径和外径要符合 PCB 制造厂家的工艺能力通常最小孔径 0.3mm。4. 丝印与标识布线完成后别忘了调整丝印层Silkscreen。将元件的标号如 R1 U2和值如 10k 0.1uF清晰地放在元件旁边但不要放在焊盘上否则会被焊接的焊锡覆盖。在 PCB 的空白处可以添加项目名称iO Watch、版本号、你的名字和日期。这不仅是标记更是一种仪式感。最后运行 DRC 检查确保没有违反任何设计规则。然后在“文件” - “导出”中生成 Gerber 文件和钻孔文件就可以发给 PCB 制板厂如嘉立创生产了。4. 软件架构与低功耗编程硬件是躯体软件则是灵魂。iO Watch 的软件核心在于实现一个高效、可靠的事件驱动系统并最大化地利用睡眠模式来节省电能。4.1 开发环境搭建与库管理开发环境就是 Arduino IDE。你需要安装针对 ATmega328P 的板支持包。如前所述选择“Arduino Pro or Pro Mini”板型处理器选“ATmega328P (3.3V, 8MHz)”。编程器可以选择 USBasp 或者 USBtinyISP 等通过 ICSP 接口对 PCB 上的 ATmega328P 进行烧录。必需的库有两个1.RTClibby Adafruit用于与 DS3231 通信提供了非常友好的 API 来读取、设置时间和闹钟。2.U8g2或Adafruit_SSD1306用于驱动 OLED 显示屏。U8g2 支持更多种显示器功能强大Adafruit 的库则更轻量。根据你的屏幕型号选择。4.2 主程序逻辑中断唤醒与状态机程序的主体结构是一个超级循环super loop但大部分时间都阻塞在睡眠函数里。#include Wire.h #include RTClib.h #include avr/sleep.h #include avr/power.h RTC_DS3231 rtc; volatile bool timerInterruptFlag false; // 中断服务程序每秒触发一次 void timerISR() { timerInterruptFlag true; } void setup() { // 初始化串口仅调试用正式版可关闭 Serial.begin(9600); // 初始化 I2C Wire.begin(); // 初始化 RTC if (!rtc.begin()) { Serial.println(F(Couldnt find RTC)); while (1); } // 如果 RTC 失去电力则设置时间仅首次需要 if (rtc.lostPower()) { rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); } // 配置 DS3231 的 SQW 引脚输出 1Hz 方波 rtc.writeSqwPinMode(DS3231_SquareWave1Hz); // 配置 MCU 的外部中断引脚连接 DS3231 的 SQW/INT pinMode(2, INPUT_PULLUP); // INT0 对应 Arduino Pin 2 attachInterrupt(digitalPinToInterrupt(2), timerISR, FALLING); // 关闭未用的外设以省电 power_adc_disable(); power_spi_disable(); power_twi_disable(); // 注意如果要用 I2C不能关闭 TWI power_timer0_disable(); // 关闭 delay() 依赖的 timer0 // power_timer1_disable(); // 根据需求保留 // power_timer2_disable(); // 根据需求保留 } void loop() { if (timerInterruptFlag) { timerInterruptFlag false; // 清除标志 // 1. 唤醒后首先读取 RTC 时间 DateTime now rtc.now(); // 2. 更新显示这里简化实际需调用显示库 updateDisplay(now); // 3. 检查按钮状态非阻塞式扫描 checkButtons(); // 4. 处理其他任务如闹钟判断 checkAlarm(now); // 5. 所有任务完成后重新进入睡眠 enterSleep(); } // 如果没有任何中断标志理论上不会执行到这里 // 但为防止意外可以加一个安全睡眠 enterSleep(); } void enterSleep() { set_sleep_mode(SLEEP_MODE_PWR_DOWN); sleep_enable(); sleep_mode(); // 进入睡眠等待中断唤醒 // 唤醒后从这里继续执行 sleep_disable(); }关键点解析中断服务程序ISR要短timerISR()只设置一个标志位不做复杂操作。所有实际工作都在loop()中根据标志位来执行。关闭未用外设power_*_disable()系列函数可以关闭 ADC、SPI、TIMER 等模块的时钟显著降低功耗。但注意如果要用 I2CTWI就不能关闭power_twi_disable()。睡眠模式选择SLEEP_MODE_PWR_DOWN是最省电的模式。唤醒源需要提前配置好这里我们用了外部中断 INT0。volatile关键字在 ISR 中修改的全局变量如timerInterruptFlag必须声明为volatile防止编译器优化导致错误。4.3 时间显示与用户交互时间显示通常需要用到图形库。以 U8g2 为例你需要根据你的 OLED 型号初始化一个对象。在updateDisplay()函数中调用库函数绘制时间字符串。为了省电可以在不操作手表一段时间后让屏幕进入休眠如果屏幕支持或者直接关闭显示。用户交互主要通过物理按钮。按钮应连接到 MCU 的具有外部中断唤醒功能的引脚如 INT0 INT1 或其他 PCINT 引脚。在checkButtons()函数中使用非阻塞式的方式检测按钮长短按实现模式切换如时间模式、秒表模式、时间调整等功能。注意防抖处理可以在软件中通过延时判断实现。5. 焊接、组装与调试实录当 PCB 从工厂回来散发着崭新的光泽和淡淡的板材气味时最激动人心的环节就开始了。但这也是最容易出错的阶段需要极大的耐心和细致的操作。5.1 焊接顺序与技巧顺序很重要遵循“先矮后高先内后外”的原则。先焊接高度最低的元件如电阻、电容、二极管然后是芯片座如果用了、IC最后是连接器、按钮等较高的元件。贴片阻容元件0805或0603封装使用烙铁和镊子。在焊盘上点少量锡用镊子夹住元件对准位置用烙铁加热焊盘上的锡使其熔化元件会自行归位。然后焊接另一侧。熟练后可以使用“拖焊”技巧一次性焊接多个引脚。QFP 封装芯片ATmega328P-AU这是难点。推荐使用焊膏和热风枪。在 PCB 的芯片焊盘上涂抹适量焊膏不要太多用镊子将芯片精确对准注意方向第1脚标记要对准 PCB 上的白点或缺口标记。然后用热风枪以 300-350°C 的温度中等风量均匀加热芯片及其周围区域直到看到焊膏熔化、变成光滑的锡球并将所有引脚“拉”向焊盘表面张力效应。停止加热让板子自然冷却。务必等完全冷却后再通电冷却后用放大镜检查是否有桥接短路或虚焊。桥接可以用吸锡带或细头烙铁修复。DS3231SN焊接方法与 ATmega328P 类似但引脚更细需要更小心。注意焊接 QFP 芯片时热风枪不要在一个点停留过久要画小圈均匀加热。可以在芯片周围放一些大的接地焊盘或元件作为“热沉”帮助均匀散热防止局部过热损坏芯片。5.2 上电前检查与“烟雾测试”焊接完成后千万不要急于上电。先进行彻底的外观检查用放大镜仔细检查所有焊点确保无桥接、无虚焊。检查元件方向二极管、电解电容、芯片的 1 脚方向是否正确。用万用表二极管档或电阻档测量电源VCC和地GND之间的电阻。在未上电、未插芯片时应该有一个较大的阻值几百 kΩ 以上。如果电阻很小如几欧姆说明存在严重短路必须排查。确认无误后进行“烟雾测试”即首次上电。建议使用一个可调限流的实验室电源将电压设为 3.3V电流限制定在 50mA 左右。接上电源观察电流读数。正常情况下空载电流只有 LDO 和 DS3231应该很小几十微安。如果电流瞬间飙升并触发限流说明有短路立即断电排查。如果电流正常再用手触摸各个主要芯片检查是否有异常发热。5.3 分模块调试系统复杂不要指望一次成功。必须分模块调试。1. 电源模块首先确保 LDO 输出是稳定的 3.3V。测量 VCC 网络上的电压。2. 编程接口连接 USBasp 编程器到 PCB 的 ICSP 接口MISO MOSI SCK RESET VCC GND。在 Arduino IDE 中选择正确的编程器和板型尝试“烧录引导程序”或上传一个最简单的 Blink 程序需要先接一个 LED 到某个 IO 口测试。如果失败检查 ICSP 连线、复位电路通常需要一个 10kΩ 上拉电阻到 VCC以及晶振如果用了外部晶振是否起振。3. RTC 模块在主程序中先不进入睡眠初始化 I2C 和 RTC 后在loop()里每秒读取并串口打印时间。打开串口监视器看是否能正确读取。如果读取失败检查 I2C 连线SDA SCL 是否接反、上拉电阻是否焊接、以及 DS3231 的电源和地址0x68。4. 显示模块单独测试 OLED 屏幕。写一个简单的测试程序初始化屏幕并显示一些文字或图形。确保屏幕的电源、I2C 地址通常是 0x3C 或 0x3D正确。5. 低功耗测试当所有模块都工作正常后再整合低功耗逻辑。上传完整的睡眠程序。用万用表电流档µA 档串联在电池和 PCB 之间测量系统平均电流。在深度睡眠时电流应降至 20µA 以下。每秒唤醒刷新显示时会有一个短暂的电流脉冲可能到几个 mA但平均电流应非常低。如果睡眠电流过大检查是否有关闭所有不必要的外设如 ADC、未用的 IO 口应设置为输入并上拉、是否有元件漏电如 LED 未完全断开。6. 外壳设计与穿戴体验优化一个裸露的 PCB 还不能称之为手表。3D 打印是制作原型外壳的绝佳方式。6.1 3D 建模与结构设计使用 Fusion 360 或 FreeCAD 等软件进行建模。设计时需要考虑PCB 固定在壳体内设计支柱standoff和螺丝孔与 PCB 上的安装孔对应。可以使用 M2 或 M1.6 的铜柱和螺丝。屏幕开窗精确测量 OLED 屏幕的显示区域和外形在壳体正面开出相应的窗口。窗口边缘最好设计一个台阶用于承托屏幕并防止漏光。按钮开孔与手感按钮的开孔需要比按钮柱直径稍大以确保不卡住。可以在按钮柱和壳体之间设计微小的间隙0.1-0.2mm。为了获得更好的手感可以在 PCB 上使用贴片轻触开关然后在壳体内设计一个柔软的硅胶按钮帽来传递按压力。电池仓设计一个易于更换电池的仓盖。可以使用卡扣式或螺丝固定式。确保电池触点通常是弹簧或弹片被可靠地固定。表带连接在壳体两侧设计标准的 20mm 或 22mm 生耳Spring Bar安装孔这样就可以使用市面上通用的手表快拆表带了。6.2 打印与后处理选择 PLA 或 PETG 材料进行打印。打印分辨率建议在 0.1mm-0.2mm 层高以获得更光滑的表面。打印完成后可能需要一些后处理去除支撑小心地去除所有支撑材料。打磨使用细砂纸如 800 目、1000 目打磨结合线、毛刺和粗糙的表面。试装配将 PCB、屏幕、电池等所有部件装入外壳检查是否干涉按钮是否顺畅屏幕是否对正。表面处理可选可以喷涂底漆和面漆来获得更好的外观和质感。甚至可以进行抛光或电镀处理。6.3 穿戴舒适性与可靠性最后将表带装上。皮革、尼龙或橡胶表带各有风格。戴在手上感受一下重量和平衡。检查手表的所有功能时间显示是否清晰按钮是否好按续航是否如预期。在这个过程中你可能会发现一些需要改进的地方比如某个按钮太硬需要调整硅胶帽的硬度或者外壳边缘有点硌手需要打磨圆润又或者屏幕在阳光下看不清考虑是否需要更换成反射式屏幕。这些迭代的过程正是 DIY 项目的魅力所在——它永远没有真正的“完成”只有不断的“优化”。从一颗芯片的灵感到手腕上滴答走时的作品iO Watch 的项目旅程充满了挑战与惊喜。它教会你的远不止如何焊接一块芯片或写一段代码更是如何系统性地思考问题、拆解问题、并动手解决问题。当你的代码通过自己设计的电路驱动着自己布局的 PCB最终在手腕上显示出准确的时间时那种成就感是无可替代的。希望这篇详尽的记录能为你点亮自己创造之路的一盏灯。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进