基于Micro:bit的二进制时钟制作:从原理到实现的趣味编程项目
1. 项目缘起从“看时间”到“解谜题”的时钟革命几年前我在一个极客社区里第一次看到二进制时钟的图片当时的感觉很奇妙一排排闪烁的LED灯没有数字没有指针却声称能告诉你时间。我盯着它看了足足一分钟才勉强猜出个大概。这种“解码”时间的体验与传统时钟的“读取”截然不同它更像是一个与设备互动的小游戏。从那时起我就一直想亲手做一个。最近手头正好有几块闲置的Micro:bit这个想法又重新燃起。为什么不做一个极简、有趣又能作为编程和硬件入门绝佳教具的“Micro Binary Clock”呢这个项目的核心就是用Micro:bit这块小巧的开发板配合其板载的5x5 LED点阵来显示二进制编码的时间。它不追求极致的精度毕竟Micro:bit没有硬件RTC而是专注于实现二进制时钟的核心逻辑与交互趣味。通过MakeCode图形化编程或Python我们可以将时、分、秒的十进制数值转换为二进制并映射到点阵上让每一个亮起的LED都代表一个二进制位。最终你将得到一个可以佩戴、可以摆放在桌面的“时间谜题”每一次看时间都是一次对逻辑思维的小小锻炼。无论是作为个人创意项目还是用于STEM教育它都充满了魅力。2. 核心原理拆解二进制如何“画”出时间要理解二进制时钟首先得忘掉我们熟悉的“12:34:56”这种格式。在二进制世界里一切数字都由0和1构成。我们的目标就是把小时、分钟、秒这些数字“翻译”成由LED灯亮代表1灭代表0组成的图案。2.1 时间数据的二进制分解以24小时制为例小时的范围是0-23。用二进制表示最多需要5位2^532足以覆盖0-31。分钟和秒的范围是0-59用二进制表示最多需要6位2^664足以覆盖0-63。所以一个完整的二进制时钟理论上需要5位小时 6位分钟 6位秒 17个独立的LED指示灯。这显然超出了Micro:bit板载5x5点阵共25个LED的显示能力但我们可以通过巧妙的布局和分时显示来解决。2.2 Micro:bit的显示策略与编码方案Micro:bit的LED点阵是共阴极结构我们可以通过编程精确控制每一个LED的亮灭。为了在有限的25个LED上显示17位二进制信息常见的策略有两种方案一分栏式布局直观但需解释这是最经典的做法。将5x5点阵划分为三栏分别代表小时、分钟、秒。最左栏5行显示小时的5个二进制位。从下往上或从上往下分别代表二进制的第0位最低位2^01到第4位最高位2^416。例如小时为18二进制10010则第1行2^1和第4行2^4的LED点亮。中间栏6行实际占用5行中的一部分通常最上一行或最下一行留空显示分钟的6个二进制位。同样从下往上排列。最右栏6行显示秒的6个二进制位。这种布局一目了然但需要使用者记住每一行对应的二进制权重。为了更友好我们可以在点阵最左侧或最下方用一颗常亮的LED作为“最低位”的标识。方案二滚动/分时显示节省空间降低实时性考虑到5x5点阵同时显示17位信息过于拥挤我们可以选择只显示“小时”和“分钟”忽略秒对于时钟来说分钟级精度通常足够。这样只需要11位56就可以比较宽松地布局在点阵上甚至可以用不同颜色通过控制LED亮度模拟来区分小时和分钟。或者采用分时显示默认界面显示小时和分钟当用户按下按钮A时切换为显示分钟和秒按下按钮B时切换回主界面。这样既保证了信息的完整性又保持了显示的清晰度。在本项目中我将采用方案一的分栏式布局并同时显示小时、分钟和秒为了适配25个点阵会对秒的显示做一点简化例如只显示秒的个位数或高3位以追求功能的完整性和教学的直观性。2.3 时间来源软件模拟RTC vs. 外部硬件RTC这是任何电子时钟项目都无法回避的核心问题时间从哪里来1. 软件模拟RTC实时时钟Micro:bit主控芯片nRF51822/nRF52833内部没有独立的硬件RTC模块。但是我们可以利用其内部的低频时钟源LFCLK和定时器/计数器在软件层面模拟一个RTC。MakeCode和MicroPython都提供了高级别的“RTC”类其底层就是基于这种原理。优点无需任何外部元件成本最低实现最简单。在MakeCode中只需使用“设置时间”和“当前时间”积木即可。缺点精度和可靠性是最大问题。当主板断电后模拟RTC将完全丢失时间归零。即使不断电其走时精度也受温度、电压波动影响日误差可能在几秒到几十秒。它依赖于主电源或电池持续供电。2. 外部硬件RTC模块如DS3231这是追求精度的方案。DS3231是一款高精度、带温度补偿的硬件RTC芯片自带电池座即使主系统完全断电其内置的纽扣电池也能保证时钟持续运行数年且精度极高月误差仅±2分钟。优点超高精度断电保持专业可靠。缺点需要额外购买模块占用I/O口通常使用I2C接口增加成本和接线复杂度。选择建议对于“Micro Binary Clock”这个趣味性和教育性优先的项目我强烈推荐先使用软件模拟RTC。它的首要目标是演示二进制显示逻辑和编程思想而非成为一个精准的计时工具。先让核心功能跑起来获得成就感。如果后续你对精度有要求再考虑升级到DS3231模块那将是另一个有趣的扩展实验。3. 手把手实现基于MakeCode的图形化编程MakeCode是Micro:bit最友好的入门编程环境。我们将通过积木编程一步步实现二进制时钟的核心功能。3.1 初始化设置与时间校准首先我们需要一个途径来设置初始时间。Micro:bit没有屏幕和键盘我们将利用其两个按钮A和B和加速度计来实现一个简易的“设置模式”。变量定义创建变量设置模式布尔型、调整项数字0代表时1代表分2代表秒、临时小时、临时分钟、临时秒。进入设置模式当同时按下AB按钮时触发进入设置模式。此时设置模式设为真调整项设为0准备调小时并将当前系统时间读取到临时小时、临时分钟、临时秒变量中。时间调整逻辑按钮A在设置模式下按下A按钮当前调整项对应的变量时/分/秒加1。如果达到上限小时23分钟59秒59则归零。按钮B在设置模式下按下B按钮切换调整项0-1-2-0循环同时可以设计一个简单的LED提示比如点阵显示H, M, S字母告知用户当前正在调整哪个部分。晃动校准可选但推荐当调整项为2调整秒时可以利用“当震动”积木将临时秒归零实现“对秒”功能这在很多电子表中很常见。保存并退出再次同时按下AB按钮退出设置模式。将临时小时、临时分钟、临时秒的值通过“设置时间”积木写入到Micro:bit的模拟RTC中。设置模式设为假。// 此为MakeCode积木逻辑的文字描述 当 按钮 AB 被按下 设置模式 设为 真 调整项 设为 0 临时小时 设为 当前时间的小时 临时分钟 设为 当前时间的分钟 临时秒 设为 当前时间的秒 当 按钮 A 被按下 如果 设置模式 为 真 如果 调整项 等于 0 临时小时 设为 (临时小时 1) % 24 否则 如果 调整项 等于 1 临时分钟 设为 (临时分钟 1) % 60 否则 临时秒 设为 (临时秒 1) % 60 当 按钮 B 被按下 如果 设置模式 为 真 调整项 设为 (调整项 1) % 3 // 可以在这里添加显示H/M/S的提示 当 震动 如果 设置模式 为 真 且 调整项 等于 2 临时秒 设为 03.2 二进制转换与LED映射函数这是项目的核心算法。我们需要一个函数输入一个十进制数如小时18和该数对应的二进制位数如5输出一个数组或一系列布尔值告诉点阵哪一行该亮灯。在MakeCode中我们可以利用“数学”类别里的“除”和“余数”积木通过循环计算来得到每一位二进制值。创建函数创建一个名为“十进制转二进制位”的函数接受参数数值和位数。计算逻辑初始化一个长度为位数的数组位数组。用一个循环从最低位i0计算到最高位i位数-1。在每次循环中计算当前位值 数值 / (2的i次方) % 2。这里的“/”是整数除法。如果当前位值等于1则将位数组[i]设为真否则为假。返回结果函数返回这个位数组。接下来创建另一个函数“显示二进制时间”它负责获取当前时间调用上面的转换函数并最终点亮LED。获取时间读取当前的小时、分钟、秒。转换分别调用三次“十进制转二进制位”函数得到小时位数组5位、分钟位数组6位、秒位数组6位。为了适应25点阵我们可以选择只取秒的高3位或低3位来显示这里假设我们显示秒的低5位这样总位数56516可以排列。映射到点阵我们需要定义一个映射规则。例如点阵的x坐标0-1列显示小时的5位y坐标0-4行。x坐标2-3列显示分钟的6位y坐标0-5行第5行可能用两个LED合并显示一位或舍弃一位。x坐标4列显示秒的5位y坐标0-4行。 遍历这些坐标根据对应的位数组值使用“绘制点”积木来点亮或熄灭LED。3.3 主循环与显示优化将“显示二进制时间”函数放入“无限循环”中。但这里有一个关键点不要不加延迟地快速刷新。这会导致LED闪烁严重且功耗激增。添加延时在循环末尾添加一个“暂停(毫秒) 100”的积木。这意味着每秒刷新10次对于时钟显示来说完全足够平滑又能大幅降低CPU占用和功耗。省电考量如果你希望用电池长期供电可以进一步优化。例如只在秒数变化时刷新整个屏幕甚至每分钟只刷新一次分钟和小时部分。或者利用Micro:bit的“空闲”事件或低功耗模式但这在MakeCode中实现较为复杂。视觉增强亮度调节可以通过“设置显示亮度”积木在环境光暗时调低亮度节省电量。分隔符可以让代表“32”或“16”这种较高权重的LED通常是点阵最上方或最下方的灯以不同的亮度或闪烁模式点亮作为视觉参考点。整点提示当分钟和秒都为0时让所有LED闪烁几次作为一个有趣的整点报时。4. 进阶与优化从MakeCode到MicroPython当你用MakeCode实现了基本功能后可能会感到积木编程在复杂逻辑和精细控制上有些局限。这时转向MicroPython是自然而然的选择。它将赋予你更强大的控制力和灵活性。4.1 MicroPython代码结构与RTC对象在MicroPython中操作RTC和LED点阵的代码更加直接。# 示例代码片段展示核心结构 from microbit import * import utime # 初始化RTC rtc RTC() # 首次使用需要设置时间例如rtc.datetime((2024, 5, 17, 5, 14, 30, 0, 0)) # (年,月,日,星期,时,分,秒,微秒) def decimal_to_binary_list(value, bits): 将十进制数转换为指定位数的二进制列表低位在前 return [(value i) 1 for i in range(bits)] def plot_binary_column(x_start, value, bits, y_offset0): 在指定列起始位置x_start绘制一个数值的二进制表示 bin_list decimal_to_binary_list(value, bits) for i, bit in enumerate(bin_list): if bit: display.set_pixel(x_start, i y_offset, 9) # 点亮亮度9 else: display.set_pixel(x_start, i y_offset, 0) # 熄灭 while True: # 获取当前时间 now rtc.datetime() hour, minute, second now[4], now[5], now[6] # 清屏 display.clear() # 绘制小时5位第0-1列 plot_binary_column(0, hour, 5) # 绘制分钟6位第2-3列需要处理6位与5行显示的问题 # 一种方法将第2列显示低5位第3列显示最高位第6位 plot_binary_column(2, minute, 5) # 显示低5位 if (minute 5) 1: # 判断第6位2^532 display.set_pixel(3, 0, 9) # 绘制秒低5位第4列 plot_binary_column(4, second, 5) # 降低刷新率节省功耗 sleep(100)4.2 外部RTC模块DS3231集成如果你决定追求精度集成DS3231模块是一个绝佳的进阶项目。你需要硬件连接将DS3231的SDA引脚接到Micro:bit的Pin 20SCL接到Pin 19VCC接3.3VGND接GND。DS3231的电池座装上CR2032纽扣电池。软件驱动MicroPython社区有现成的ds3231库。你需要先将该库文件上传到Micro:bit。代码修改主循环中不再从内部rtc.datetime()读取时间而是从DS3231实例读取。from microbit import * from ds3231 import DS3231 # 初始化I2C和DS3231 i2c.init(sdapin20, sclpin19) ds DS3231(i2c) # 设置时间仅第一次需要 # ds.set_time(14, 30, 0) # 时分秒 while True: # 从DS3231读取时间 hour, minute, second ds.get_time() # ... 后续显示逻辑与之前相同 ...切换策略一个更优雅的设计是在代码开始时检测DS3231是否存在。如果存在则使用它作为时间源如果不存在则回退到内部模拟RTC。这样同一个程序就同时兼容了基础版和进阶版硬件。4.3 低功耗设计与电源管理想让你的二进制时钟真正“移动”起来长时间用电池供电低功耗设计是关键。减少刷新这是最有效的方法。将主循环中的sleep(100)改为sleep(900)接近每秒刷新一次。对于时钟这完全可接受。关闭无线电如果你不用蓝牙或无线电在程序开头添加radio.off()和bluetooth.stop()。利用深度睡眠MicroPython支持deep_sleep。你可以让MCU大部分时间休眠只靠RTC外部DS3231的闹钟功能来定时唤醒例如每分钟唤醒一次更新显示。但这需要外部RTC支持且编程更复杂涉及中断唤醒。硬件改造Micro:bit的板载USB接口和边缘连接器上的3.3V稳压电路即使在关机时也有微小电流消耗。对于极致续航可以考虑使用独立的低压差稳压器LDO直接给核心供电并断开不必要的电路。5. 项目总结与扩展思考完成这个“Micro Binary Clock”项目后我最大的体会是简单的想法通过层层拆解和实现能带来远超预期的学习收获和成就感。从理解二进制编码到在有限硬件上设计显示布局再到处理时间源的精度的妥协与进阶最后思考如何让它更省电、更耐用——这几乎是一个微型产品开发的完整闭环。几个我踩过或想到的“坑”与技巧LED映射混乱在编写映射函数时最容易搞混二进制位的顺序高位在前还是低位在前和点阵坐标的方向原点(0,0)在左上角。我的建议是先在纸上画好点阵图标出每个位置代表的二进制权重写死几个测试时间如12:00:00 18:30:15手动计算出LED应该怎么亮然后用这个作为测试用例来调试你的程序事半功倍。时间漂移的接受度使用内部RTC一两天下来差个几分钟很正常。如果这让你困扰要么接受它“趣味装置”的定位要么就上DS3231。把它看作一个明确的“可升级点”而不是项目的缺陷。按钮防抖在设置时间的逻辑中快速按动按钮可能会导致一次按下被识别为多次。在MicroPython中简单的防抖可以在检测到按键后加一个短暂的sleep(50)并等待按键释放。扩展玩法网络授时如果你有Micro:bit V2并搭配Wi-Fi扩展板可以尝试从NTP服务器获取精确时间实现“自动对时”。多彩显示虽然单色LED但可以通过PWM控制亮度用不同亮度来表示不同的时间单位如小时最亮分钟中等秒最暗。创意外壳用3D打印或激光切割为它制作一个精致的外壳立刻变成一件独特的桌面摆件或可穿戴的极客饰品。教学工具这是它最大的价值所在。你可以清晰地展示二进制与十进制的转换过程甚至可以设计一个“测验模式”在点阵上显示一个二进制图案让使用者通过按钮输入对应的十进制时间。这个项目就像一把钥匙它打开的不只是一扇显示时间的新窗口更是通向嵌入式系统、低功耗设计、传感器集成和创造性问题解决的大门。当你看着那些由自己编写的逻辑所控制、规律闪烁的LED清晰地指示着时间时那种感觉远比直接看手机屏幕上的数字要有趣得多。