7元成本打造旋转LED显示:ATtiny13视觉暂留项目全解析

1. 项目缘起:从“7块钱能干啥”到桌面上的流光溢彩

前几天在工作室整理零件盒,翻出来几颗尘封已久的ATtiny13单片机。这玩意儿现在便宜得离谱,一颗也就一两块钱。当时就在想,现在7块钱连杯像样的奶茶都买不到,但用这点成本,能不能做出点让人眼前一亮的东西?这个念头一冒出来,就收不住了。正好手边还有一堆用剩的LED和一块废旧的电机,一个点子瞬间成型:做一个基于视觉暂留原理的旋转式显示器,也就是常说的PoV显示器。

PoV,全称Persistence of Vision,视觉暂留。这原理其实我们从小就知道,快速挥动一根发光的荧光棒,你看到的就是一条光带,而不是一个光点。利用这个原理,让一排LED高速旋转起来,通过精确控制每个LED在特定位置的点亮与熄灭,就能在空中“画”出字符、图案甚至动画。这听起来很酷,但网上很多教程要么用的单片机比较贵,要么电路复杂。我的目标很明确:极致成本控制,就用最便宜、资源最少的ATtiny13,加上最基础的元件,把这事儿给办了。

最终做出来的效果,远超预期。当电机转起来,蓝色的LED光迹在空中稳定地显示出“HELLO”和一颗爱心时,那种成就感,绝不是花7块钱买杯饮料能比的。这不仅仅是一个电子制作,更是一次对“极简设计”和“资源压榨”的实践。下面,我就把这套从硬件选型、电路设计、软件编程到调试校准的完整过程,毫无保留地分享出来。你会发现,单片机编程和硬件DIY的门槛,远没有想象中那么高。

2. 核心器件选型与成本拆解:如何把每一分钱花在刀刃上

做低成本项目,选型是第一步,也是最关键的一步。每一毛钱都要精打细算,同时还要保证功能实现。这是我的最终物料清单和选型思考:

主控芯片:ATtiny13A

  • 成本:约1.5元/片。
  • 为什么是它?这是整个项目的灵魂。ATTiny13是Atmel(现Microchip)8位AVR单片机家族中资源最少、体积最小、价格最低的成员之一。它只有1KB的Flash(用于存程序)、64字节的SRAM、5个I/O口。用这样一个“玩具级”的MCU来做动态显示,本身就是最大的挑战和乐趣。市面上更常见的ATtiny85(约5元)或Arduino Nano(约15元)当然更容易,但就失去了“7块钱”的极致意义。选择ATtiny13,就是选择在极限约束下跳舞。

显示单元:5mm 高亮蓝色LED

  • 成本:约0.1元/个,8个共0.8元。
  • 为什么是蓝色?为什么是5mm?蓝色LED的波长较短,在视觉暂留显示中,相比红色或绿色,其光迹在暗环境下显得更清晰、更“有科技感”。5mm直径是最通用的规格,亮度足够,也方便焊接。数量定为8个,是权衡的结果:太少则显示字符高度不足,太多则ATtiny13的I/O口和驱动能力都不够(我们还需要一个I/O口用于同步信号)。

驱动与旋转部分:N20微型减速电机 + L9110S电机驱动模块

  • 成本:电机约2.5元,驱动模块约0.8元。
  • 电机的选择:N20电机体积小、扭矩合适、转速稳定(我选用的是3-6V供电,每分钟约200-300转的型号)。转速是关键,太慢则画面闪烁,太快则对程序时序要求极高且可能甩散架。200-300转是一个经过验证的、能产生稳定视觉暂留效果的区间。
  • 为什么需要驱动模块?ATtiny13的I/O口输出电流很小(通常20mA左右),无法直接驱动电机。L9110S是一个双路H桥驱动芯片,可以用单片机的小信号控制电机的启停和方向,这里我们只用到它的一路。它价格低廉,接线简单,是性价比之选。

同步与供电:槽型光电开关 + 杂散元件

  • 成本:光电开关约0.5元,电阻、排针、洞洞板、电池盒等约1元。
  • 同步的重要性:PoV显示要稳定,必须知道每一圈旋转的起始点在哪里。槽型光电开关(也叫光遮断器)配合固定在旋转轴上的一个小挡片,可以在每圈转到特定位置时产生一个脉冲信号。这个信号就是整个显示系统的“心跳”,所有LED的点亮时序都以此为零点进行推算。
  • 供电:采用4节5号电池盒(约6V)为整个系统供电,同时通过一个AMS1117-5.0稳压芯片(约0.3元)为ATtiny13提供稳定的5V工作电压。电机则直接使用电池的6V驱动,动力更足。

总成本核算: ATtiny13(1.5) + LEDx8(0.8) + 电机(2.5) + 驱动(0.8) + 光电开关(0.5) + 稳压/电阻/板子等(1.0) = 7.1元。 看,完美贴合“7块钱”的主题!即使你手头什么都没有,全部网购,算上运费可能也就10元左右。这个清单体现了电子DIY的一个核心思路:利用最通用、最廉价的元件,通过设计和编程,实现有趣的功能。

3. 电路设计与焊接要点:在洞洞板上搭建旋转的艺术

电路原理并不复杂,但因为是旋转装置,可靠性是第一位的。所有连接必须牢固,重量分布要尽量均衡。

3.1 原理图设计思路

整个系统可以分为三个部分:主控与显示板(旋转部分)、固定底座(供电与同步部分)。

旋转部分(核心)

  1. ATtiny13:VCC接5V,GND接地。我们使用其PB0-PB4五个I/O口来驱动5个LED(实际焊接了8个LED,但采用共阳或共阴接法后,还是只需要5个控制口)。具体驱动方式后面详述。
  2. LED阵列:为了用最少的I/O控制最多的LED,我采用了“共阳”接法。将所有8个LED的阳极(长脚)连接在一起,接到VCC(5V)上。每个LED的阴极(短脚)通过一个限流电阻后,分别接到ATtiny13的5个I/O口上。注意,这里不是1对1控制。通过软件扫描和LED在物理位置上的排列,5个I/O口利用人眼视觉暂留,可以控制8个LED形成一列像素。
  3. 重量平衡:将ATtiny13、电阻等较轻的元件布置在洞洞板中心附近,而8个LED则沿着板子的一条长边均匀排列。焊接时尽量保证对称,避免旋转时因重心不稳导致剧烈震动。

固定底座部分

  1. 电机与驱动:电池正负极接入L9110S模块的VCC和GND。ATtiny13的一个I/O口(如PB5)连接到L9110S的输入控制端IA(或IB),通过输出高/低电平来控制电机的转/停。电机的两根线接在L9110S的输出端。
  2. 同步传感器:槽型光电开关的VCC和GND接入5V和地。其输出信号线(OUT)通常是一个集电极开路输出,需要接一个上拉电阻(如10KΩ)到5V,然后连接到ATtiny13的另一个I/O口(如PB2)。当挡片穿过槽时,输出会从高电平变为低电平,产生一个下降沿脉冲。
  3. 供电:电池的6V先经过AMS1117-5.0稳压芯片降到5V,再给ATtiny13和光电开关供电。电机驱动模块直接接6V电池,以确保转速。

3.2 焊接与组装实战经验

注意:旋转部分的所有焊点都必须饱满、牢固。可以用手轻轻拨动元件,确认没有虚焊。松动的元件在高速旋转下很可能被甩飞。

  1. 先静后动:先在洞洞板上焊接好旋转部分的所有电路,包括单片机、LED、电阻。先不要接电机。用USBasp等编程器给ATtiny13烧录一个简单的测试程序(比如让LED轮流点亮),用外部5V电源供电,测试所有LED是否受控。这一步确认了核心功能正常。
  2. 处理引线:电机需要两根线供电,旋转板上的VCC和GND也需要从底座引上来。这里有个关键技巧:不要使用普通的杜邦线!它们太硬,且接头不牢。应该使用细软的硅胶线,并且焊接在洞洞板和电机接线端子上。对于旋转部分与固定部分之间的电源连接,最理想的方法是使用滑环,但为了成本,我们采用更直接(但也更考验手艺)的方法:将电机固定在底座上,电机的转轴直接与旋转洞洞板机械固定(可以用胶粘或螺丝夹紧),这样电机的两根线自然就成了旋转部分的供电通道。而同步信号线则不需要旋转连接,因为光电开关是固定在底座上的。
  3. 同步挡片的安装:在电机的转轴(或与转轴固定在一起的旋转板背面),用热熔胶或胶水粘上一小片不透明的材料(如剪裁的塑料片、黑色电工胶带)。调整其位置,确保它旋转时能准确地穿过固定在底座的槽型光电开关的凹槽。挡片不需要很大,宽度能遮住光槽即可,长度1-2厘米足够。
  4. 配平调试:这是保证显示不抖动的关键。将组装好的旋转部分(板子+LED)轻轻放在指尖上,找到它自然平衡的点。在较轻的一侧,点一些热熔胶或粘上一小段焊锡丝作为配重,直到它能大致保持水平。这一步做得越仔细,最终旋转起来就越平稳,显示效果越清晰。

4. 软件驱动解析:在1KB内存中编排光影之舞

这是整个项目最精华的部分。ATtiny13只有1KB的Flash和64字节的RAM,这意味着代码必须极度精简,没有操作系统,没有库函数,一切都要直接操作寄存器。

4.1 开发环境与熔丝位配置

我使用的是PlatformIO IDE + AVR平台,也可以使用Arduino IDE配合arduino-tiny核心,或者最原始的Atmel Studio。对于ATtiny13,熔丝位配置至关重要,配置错误会导致芯片无法编程或工作异常。

  • 时钟源:为了省掉外部晶振的成本和空间,我们使用芯片内部的9.6MHz RC振荡器。在熔丝位中,需要将CKDIV8编程(即设置为0),这样系统时钟就是9.6MHz,而不是默认的1.2MHz。更高的主频意味着我们能实现更精确的时序控制。
  • 其他熔丝SUT_CKSEL选择为0x69(代表使用9.6MHz内部RC,启动时间64ms)。BODLEVEL(掉电检测)可以根据需要禁用以省电,但建议在调试阶段开启一个级别(如2.7V),防止电压不稳导致程序跑飞。
  • 编程接口:使用USBasp编程器,连接ATtiny13的RESET、MOSI、MISO、SCK、VCC、GND进行烧录。

4.2 核心算法与代码实现

程序的核心逻辑是一个状态机,它不断等待同步信号,然后根据当前旋转角度,查表输出对应的LED列数据。

第一步:定义显示数据我们要显示“HELLO”和一个爱心图案。假设我们的LED阵列在物理上是一列8个LED(垂直方向)。当它旋转时,我们在不同角度点亮不同的LED组合,就能“画”出水平方向的一排像素点。 我们可以把整个360度圆周划分为若干等份,比如120份(每份3度)。那么,每个字符或图案,就由一系列(比如8行 x 10列)的二维点阵数据构成。但由于内存极其有限,我们不能存储完整的位图。一个巧妙的办法是:只存储每一列(对应旋转的某个角度位置)需要点亮的LED的二进制模式。

例如,对于字符“H”的第一列,可能只有顶部和底部的LED亮,中间不亮。那么这一列的数据可以用一个8位的字节来表示,比如0b10000001(假设最高位对应最上面的LED)。我们把要显示的所有字符的每一列数据,按顺序放在一个大的常量数组里。

// 示例:定义“HELLO”和爱心的列数据,每个字符假设宽5列,列之间空1列 const uint8_t pov_pattern[] PROGMEM = { // H (5列) 0b10000001, 0b10000001, 0b11111111, 0b10000001, 0b10000001, 0b00000000, // 空列 // E (5列) 0b11111111, 0b10001001, 0b10001001, 0b10001001, 0b10000001, 0b00000000, // ... 后续L, L, O, 爱心图案的数据 };

这里使用了PROGMEM关键字将数据存放在Flash中,而不是宝贵的SRAM里。

第二步:同步信号中断处理将连接光电开关的I/O口(如PB2)配置为外部中断输入,并设置为下降沿触发。当挡片穿过,产生下降沿脉冲时,触发中断服务程序。

ISR(INT0_vect) { // ATtiny13的PB2对应INT0中断 sync_detected = 1; // 设置一个同步标志位 column_index = 0; // 将列索引重置为0,表示新的一圈开始 }

这个中断程序要尽可能短,只做标记,不做复杂操作。

第三步:主循环与显示扫描主程序的核心是一个精确的延时循环,它决定了每一列数据显示的时间宽度,这个宽度必须与电机的转速严格匹配。

void loop() { if (sync_detected) { sync_detected = 0; // 同步后,开始显示一整圈的数据 for (uint8_t i = 0; i < TOTAL_COLUMNS; i++) { // 从Flash中读取当前列的数据 uint8_t led_data = pgm_read_byte(&pov_pattern[column_index]); // 将数据输出到控制LED的I/O口(可能需要一个简单的解码驱动,因为5个I/O控制8个LED) output_leds(led_data); // 精确延时,等待旋转到下一个“列”的位置 delay_us(COLUMN_DELAY_US); // 这个延时值需要根据实际转速校准 column_index++; if (column_index >= TOTAL_COLUMNS) { column_index = 0; // 一圈显示完毕,但通常等下一个同步信号重置更可靠 } } // 一圈显示完后,关闭所有LED,等待下一个同步信号 output_leds(0x00); } // 其他时间可以进入低功耗休眠模式以省电 }

关键难点与技巧

  1. 时序校准COLUMN_DELAY_US是核心参数。它 = (每圈时间) / (总列数)。例如,电机300转/分,即每秒5圈,每圈时间200ms。如果总列数是120,那么每列显示时间就是200ms / 120 ≈ 1.666ms。你需要用示波器或者通过反复试验调整这个延时,直到字符看起来稳定、不拉伸也不压缩。更高级的做法是,用定时器中断来产生这个精确的延时,而不是用_delay_us()函数。
  2. I/O扩展驱动:前文提到用5个I/O控制8个LED。这可以通过一个74HC595移位寄存器来实现,但为了成本,我们也可以用软件扫描。例如,将8个LED分为两组(每组4个),共用控制线,利用人眼视觉暂留,在极短时间内轮流点亮两组。但这要求MCU速度足够快。对于ATtiny13 @9.6MHz,在1.6ms内完成两次扫描输出是可行的。代码会复杂一些,但能省下一个芯片的钱。
  3. 抗干扰处理:电机是巨大的噪声源。必须在ATtiny13的VCC和GND之间紧贴芯片放置一个0.1uF的瓷片电容进行去耦。光电开关的信号线也可以加入一个小的滤波电容(如100pF)到地,并在软件中对中断信号做简单的防抖处理(如连续检测到几次低电平才认为有效)。

5. 系统调试与效果优化:让显示清晰稳定的秘诀

硬件焊接好,程序烧录进去,只是完成了第一步。要让PoV显示器清晰稳定地工作,调试环节必不可少。

5.1 上电调试流程

  1. 静态测试:不接电机,只给旋转板和底座供电。用手遮挡光电开关,观察是否有LED闪烁响应(可以在同步中断服务程序里加一个LED翻转的测试代码)。同时测试电机驱动,控制I/O口高低电平变化,听电机是否有启停的声音。
  2. 低速旋转测试:将电机供电电压降低(比如用3V),让电机慢速旋转。此时观察LED是否常亮或规律闪烁。如果所有LED常亮,可能是电源线在旋转中短路;如果毫无规律,可能是同步信号没抓到或程序时序完全不对。
  3. 同步信号捕捉:这是调试的关键。在同步中断服务程序里,让一个特定的LED(比如最边上的一个)亮一下。当电机旋转时,你应该看到一个固定的位置有一个光点。如果这个光点位置飘忽不定,说明同步信号不稳定,需要检查挡片是否安装牢固,光电开关距离是否合适(通常1-3mm为宜),以及软件防抖是否有效。
  4. 显示初现:当同步稳定后,加载最简单的显示数据(比如只显示一条竖线或一个点),调整COLUMN_DELAY_US参数。你会看到光点变成了一条短线段。继续调整,直到线段长度合适,并且位置固定。
  5. 全速运行与微调:将电压升到额定6V,让电机全速运行。此时观察完整的字符显示。常见问题及解决:
    • 字符断开、不连贯:延时太短,每列显示时间不足,导致视觉暂留衔接不上。增大COLUMN_DELAY_US
    • 字符重叠、模糊:延时太长,上一列的光迹还没消失,下一列就显示了。减小COLUMN_DELAY_US
    • 字符抖动、上下跳动:旋转部分动平衡没做好。重新进行配平。也可能是电机轴有轻微偏心,尝试调整电机固定位置或更换电机。
    • 特定位置字符扭曲:同步信号的位置与LED物理位置的角度偏移没算对。在程序中加入一个角度偏移量参数,在同步发生后,先延时一小段时间再开始显示第一列数据,通过调整这个偏移量来左右移动整个画面。

5.2 效果优化进阶

  1. 亮度与均匀性:由于LED是扫描点亮,占空比可能不足50%,导致亮度下降。可以尝试在允许的电流范围内,减小LED的限流电阻值(如从220Ω降到100Ω),但要注意不要超过ATtiny13 I/O口和LED的最大电流。也可以采用PWM调制,在点亮的那段时间里让LED以更高频的PWM全亮,提升视觉亮度。
  2. 显示内容更新:ATtiny13没有串口,如何更新显示内容?一个实用的方法是利用其片内EEPROM(64字节)。我们可以预先编写几个不同的显示图案,通过一个额外的按钮来切换。按钮按下一次,就改变一个存储在EEPROM中的“当前图案索引”,主程序根据这个索引从不同的数据数组中读取内容。这样就能实现多组显示内容的切换。
  3. 功耗控制:整个系统耗电大户是电机和LED。可以在程序中加入逻辑:当检测到一段时间没有同步信号(电机停转)时,让单片机进入深度休眠模式,关闭LED输出,极大降低静态功耗。用电池供电时,这个技巧能显著延长续航。

做完所有这些步骤,当你接通电源,电机平稳旋转,清晰稳定的光绘图案悬浮在空中时,你会觉得所有折腾都是值得的。这个成本不到10元的小项目,融合了模拟电路、数字电路、单片机编程、机械装配和调试技巧,是一个非常好的综合性入门实践。它带给你的,不仅仅是桌面上一件酷炫的装饰品,更是一种“用最少资源创造价值”的工程师思维和动手解决问题的快感。