基于Arduino与三洋CCB总线协议驱动老式音频芯片的复古音响系统 1. 项目概述与CCB总线技术解析在捣鼓老式消费电子设备尤其是上世纪八九十年代的音响、收音机时你经常会遇到一些功能强大但资料稀缺的专用芯片。它们不像今天的通用传感器或驱动器那样有现成的Arduino库其控制往往依赖于制造商私有的通信协议。三洋Sanyo的CCB总线就是这样一个典型的例子。它曾广泛应用于三洋自家的收音头、汽车音响、组合音响的显示和音频控制芯片中。最近我从一台1998年的沃尔沃汽车音响和一台老式卡带机上拆解了几块板子上面赫然印着LC72131、LC75821E、LC75342这些型号。面对这些“古董”直接丢弃未免可惜但如何让它们在现代微控制器比如Arduino上重获新生就成了一个有趣的挑战。这个项目的核心就是深入三洋CCB总线协议的内核并基于一个开源库用Arduino Nano作为大脑将这些来自不同设备的芯片整合成一个功能完整的、可通过红外遥控和编码器操作的复古音频控制系统。CCB全称Computer Control Bus是三洋半导体为其消费电子产品线内部IC互联设计的一种单主站串行通信协议。它的设计目标非常明确在单一设备如一台收音机或音响内部让一个主控制器通常是微处理器能够以最经济、最可靠的方式配置和管理多个从属芯片例如调谐器、音量控制、显示驱动等。在功能定位上它与我们更熟悉的Philips I2C总线类似但在电气连接和协议细节上又有所不同有时看起来更像SPI。理解CCB关键在于抓住其“单主站”和“同步串行”这两个特性。单主站意味着总线仲裁的复杂性被彻底消除通信永远由主设备发起从设备只是响应这极大地简化了硬件和软件设计。同步串行则意味着数据传输依赖于一个由主设备提供的时钟信号CL数据位在时钟边沿被采样保证了时序的精确性。CCB通常只需要3到4根线芯片使能CE、时钟CL、数据输入DI在需要读取从设备数据时还会用到数据输出DO。这种极简的连线方式非常适合在PCB板级进行紧凑的布局。与I2C相比CCB没有开漏总线和上拉电阻带来的电平兼容与速度权衡问题它通常采用CMOS电平直接驱动。与SPI相比CCB的CE信号功能更接近于片选但其协议帧结构是固定的包含地址段和数据段不像SPI那样灵活可变。CCB的地址和数据宽度通常是8位的倍数这为编程带来了便利。对于任何想要修复、改造或创意复用老式三洋芯片的硬件爱好者、复古设备玩家或嵌入式学习者来说掌握CCB就像拿到了一把开启特定宝藏的钥匙。它不仅仅是让旧芯片重新运转更是一种对经典硬件设计逻辑的逆向工程与再创造。2. 硬件拆解、选型与电路设计思路这个项目的硬件部分是一场“旧货利用”的盛宴核心思想是物尽其用将不同设备上的功能模块通过统一的CCB总线整合起来。我的物料来源主要是电子垃圾一台老沃尔沃车的LCD显示面板、一个旧卡带机里的收音调谐模块包含LC72131、另一个卡带机里拆出的LC75342音效芯片、一块从废旧LCD电视上切割下来的TPA3101D2 D类功放板、一个等离子电视的IR接收头以及一个三星旧遥控器。主控则是一块常见的Arduino Nano供电来自一个旧的台式机电源。2.1 核心芯片功能分析与选型理由LC72131 AM/FM PLL频率合成器这是项目的“调谐心脏”。在模拟收音机中稳定、精确地锁定电台频率依赖于锁相环技术。LC72131内部集成了可编程分频器、参考频率振荡器和相位比较器。通过CCB总线我们可以向它写入频率分频比从而控制其内部的压控振荡器VCO产生目标频率的本振信号驱动外围的收音头电路如LA1833进行选台。选择它是因为它在老式车机和音响中非常普遍资料相对齐全且通过编程它能覆盖完整的FM/AM波段。LC75821E LCD显示驱动器这是项目的“视觉界面”。它是一款静态或1/2占空比驱动的通用LCD驱动器最多可驱动53个段码。我拆下的这块屏是一个典型的车用显示模块包含3位7段主数字、一些小的7段符和一个9段符以及8个静态图标如FM/AM、立体声指示。使用专用驱动芯片而非直接GPIO驱动是因为LCD需要交流电压驱动以防止电解失效LC75821E不仅提供了这种驱动能力还通过串行接口大大节省了主控的IO口。它的每个段码都对应一个特定的数据位通过CCB发送一长串比特流来控制其亮灭。LC75342单芯片音量与音调控制系统这是项目的“音频预处理中心”。它是一个通过CCB控制的电子电位器集成了音量带平衡、高音Treble、低音Bass调节和四路输入选择功能。音量控制范围从0dB到-79dB步进1dB还有静音档。高音和低音调节范围分别为±10dB和20dB。选用它是因为它提供了完整的模拟音频通道控制且全部数字化可编程避免了机械电位器的磨损和噪声是实现高品质、可编程音频控制的关键。TPA3101D2 D类音频功放这是项目的“功率输出终端”。从旧电视板上切割而来它是一个10W每声道的高效D类功放。选择D类功放而非传统的AB类主要是出于效率和发热考虑。在有限的箱体空间内D类功放高达87%的效率意味着几乎不需要散热片这简化了结构设计。它通过两个GPIO引脚选择固定增益20/26/32/36dB我们只需提供电源和来自LC75342处理后的音频信号即可。Arduino Nano作为主控制器它需要至少4个数字IO来模拟CCB总线的CE、CL、DI、DO信号具体取决于芯片是单向还是双向通信。此外还需要引脚连接旋转编码器、IR接收头以及可能的I2C LCD作为辅助调试显示。Nano的IO数量刚好够用其5V逻辑电平也与这些老式CMOS芯片兼容。2.2 电路连接与信号流设计整个系统的信号流是线性的IR遥控或编码器输入 - Arduino Nano主控- CCB总线 - 各功能芯片。CCB总线连接这是最核心的部分。虽然三个芯片都使用CCB但它们的CE芯片使能引脚需要独立控制。因此我使用了Arduino Nano的三组不同的数字引脚来分别连接三个芯片的CE线而CL时钟和DI数据输入则可以共享因为CCB是单主站主控在同一时间只与一个从设备通信。对于需要读取数据的芯片理论上LC72131的某些状态可读DO线也需要连接。LC72131接线CE - A0,CL - A3,DI - A1,DO - A2。LC75821E接线CE - A9,CL - A10,DI - 0(D0)DO - A11虽然此屏可能只读但预留。LC75342接线CE - A5,CL - A6,DI - 1(D1)DO - A7。音频信号流收音头模块的音频输出或外部AUX输入 - LC75342的输入引脚 - LC75342处理音量/音调后的输出 - TPA3101D2功放输入 - 功放输出 - 扬声器。控制与显示旋转编码器CLK, DT接D5, D6用于手动微调频率。IR接收头数据线接D2。一个I2C接口的1602 LCD接A4, A5需注意与LC75342的CE引脚A5区分I2C是复用用于显示当前音量、频率等信息。电源整个系统需要稳定的5V为数字部分Arduino CCB芯片供电以及一个12V-24V根据TPA3101D2数据手册为功放部分供电。我从旧台式机电源取用5V, 12V和GND。注意在连接CCB总线时务必仔细查阅每个芯片的数据手册确认其引脚定义。特别是CE引脚的有效电平通常是低电平使能以及DO引脚是否为开漏输出需要上拉电阻。在我的连接中DO引脚都通过Arduino内部上拉电阻处理这在低速通信下通常可行但为了稳定性外部加上10kΩ上拉电阻是更规范的做法。3. SanyoCCB库详解与底层通信实现要让Arduino与这些三洋芯片对话我们需要一个能精确产生CCB协议时序的软件层。幸运的是GitHub上有一个由RodLophus开源的项目SanyoCCB它提供了一个轻量级的Arduino库。理解这个库的工作方式是成功驱动这些芯片的关键。3.1 CCB协议帧结构深度解析在编写或使用任何通信库前必须吃透协议本身。CCB的一帧数据主要分为两个阶段地址传输阶段和数据传输阶段均由主设备控制。起始与地址阶段主设备首先将CE信号拉低表示通信开始。在CE为低期间主设备通过DI线在CL时钟的上升沿依次发送8位地址位B0-B3, A0-A3。对于只有4位地址组的芯片高4位B0-B3需要填充为“伪数据”。这个地址唯一标识了总线上的目标从设备。数据阶段8位地址发送完毕后主设备将CE信号拉高。在CE为高期间主设备继续在CL上升沿通过DI线发送数据位D0-Dn。数据的位宽通常是8的倍数如8, 16, 24, 32位具体长度取决于芯片和操作。结束所有数据位发送完毕后主设备将CE拉低结束本次通信。读操作如果需要从从设备读取数据过程类似。在CE拉高进入数据阶段后从设备会在CL的下降沿将数据放到DO线上主设备则在随后的CL上升沿从DO线采样数据。SanyoCCB库的SanyoCCB类构造函数正是封装了这四个引脚。其write方法则严格按照上述时序将地址和数据数组发送出去。// 库的核心初始化CCB总线定义DO, CL, DI, CE引脚 SanyoCCB Radioccb(A2, A3, A1, A0); // Pins: DO CL DI CE // 写入数据address是8位芯片地址data[]是数据数组len是数组长度 Radioccb.write(B10000010, pll_in1, 3);3.2 针对LC72131 PLL的寄存器配置实践LC72131的配置相对复杂需要通过多个字节通常是3个来设置其工作模式、分频比等。在代码中我定义了三个字节数组pll_in1[3]和pll_in2[3]来存储这些配置。每个字节的每一位都对应芯片内部寄存器的一个特定功能。例如设置FM波段。这不仅仅是一个开关而是一系列相关寄存器的协同设置IN10_DVS1选择FM的VCO分频器模式。IN10_CTE1使能可编程分频器。IN10_XS1选择外部晶体振荡器作为参考时钟源。IN10_R0-R3设置参考频率分频比这里R01, R10, R20, R30结合其他设置最终产生25kHz的参考频率步进。IN20_GT11设置FM波段选择。IN22_BO40在写入频率时先进入静音模式避免调谐过程中的噪音。调谐频率的计算是核心。对于FM本振频率VCO 目标电台频率 中频IF通常10.7MHz。然后根据公式分频比N VCO频率 / (参考频率 * 预分频比)来计算。在代码中我进行了简化处理uint16_t LC72131Tune(uint16_t frequency) { // frequency: FM为MHz*10, AM为kHz/10 uint16_t fpd 0; // 分频比 switch(band) { case LC72131_BAND_FM: // 简化计算: fpd (目标频率MHz*10 中频10.7MHz*10) / (步进50kHz*2 / 10) // 即: fpd (frequency 107) fpd (frequency 107); // 例如94.9MHz - frequency949, fpd1056 break; case LC72131_BAND_AM: // AM: fpd ((frequency IF) / 10) 4 fpd (frequency 45) 4; // IF450kHz break; } // ... 将fpd写入pll_in1[1]和pll_in1[2] ... }实操心得LC72131的数据手册中分频比的计算公式涉及预分频器Pulse Swallower和可编程计数器初次接触会感到困惑。一个实用的技巧是先在网上搜索已有的驱动代码如针对某些品牌收音机的Arduino项目参考其计算方式再对照数据手册理解。另一个关键点是在写入新的频率分频比之前需要先将芯片置于“静音”或“调谐”模式通过设置IN22_BO4等位写入完成后再恢复正常模式否则可能会听到刺耳的调谐噪声。3.3 驱动LC75821E LCD的段码映射技巧驱动自定义LCD段码屏是最繁琐但也最有成就感的一步。LC75821E需要一次性写入56位数据来控制所有段。我们需要建立一个字节数组如Data[7]共56位并精确地知道屏幕上每一个段每一小划对应这56位中的哪一位。获取段码映射表这是最关键的步骤。通常需要查阅LC75821E的数据手册里面会有一个“Segment Output Assignment”表格。但更直接的方法是“暴力测试”写一个简单的循环程序依次将56位中的每一位置1同时其他位置0然后观察屏幕上哪个段亮起从而手动构建映射关系。我为此花费了几个小时但一劳永逸。创建数字字体库一旦有了映射表就可以为每个数字0-9定义其对应的位模式。例如对于最左边的第一位数字其7段a,b,c,d,e,f,g可能对应Data[3]的D1, D0, D2, D4, D6, D7, D3位。数字“1”需要点亮b和c段那么对应的代码就是void Digit_1(uint8_t number) { switch(number) { case 1: bitWrite(Data[3], D1, 0); // 段b亮 (假设低电平驱动) bitWrite(Data[3], D0, 1); // 段c亮 bitWrite(Data[3], D2, 1); // 段a灭 bitWrite(Data[3], D4, 0); // 段d灭 bitWrite(Data[3], D6, 0); // 段e灭 bitWrite(Data[3], D7, 0); // 段f灭 bitWrite(Data[3], D3, 0); // 段g灭 break; // ... 其他数字0,2-9 ... } LCDDisplay.write(B11111111, Data, 7); // 写入整个56位数据 digitalWrite(INH, HIGH); // 使能显示根据芯片INH高电平可能为显示开启 }注意bitWrite(Data[3], D1, 0)中的0或1取决于LCD是正显还是反显以及芯片的驱动逻辑。需要根据实际效果调整。通常数据位为0时对应段点亮。优化与封装为了避免每次更新显示都重写全部56位可以维护一个全局的Data数组。更新数字时只修改该数字对应的那几个位然后调用write函数刷新整个显示。对于图标同理。3.4 配置LC75342实现音频控制逻辑LC75342的配置相对直观它需要40位数据5字节。我们需要按照其数据手册定义的位域来设置输入选择、增益、音量、高音、低音等。定义控制位像处理LC72131一样为每个功能位定义清晰的宏。// 输入选择控制位 (在byte 0) #define LC75_L1_R1 0 // 输入1 #define LC75_L2_R2 1 // 输入2 // ... 音量控制位 (在byte 1), 高/低音控制位 (在byte 2) ...创建控制函数为每个功能编写独立的函数这些函数只修改全局控制数组SignalChipControl[5]中相应的位。void VolumeControl(byte vol) { // vol: 0-80 (0对应0dB, 80对应-79dB或静音) // 将vol的8位值分解到寄存器的各个位上 bitWrite(SignalChipControl[1], LC75_V0, bitRead(vol, 7)); // ... 写入其他位 LC75_V1 到 LC75_V7 ... } void InputSwitchingControl(byte input) { // input: 1-4 // 每次只使能一路输入 bitWrite(SignalChipControl[0], LC75_L1_R1, (input 1)); bitWrite(SignalChipControl[0], LC75_L2_R2, (input 2)); // ... }应用设置任何控制函数被调用后都需要将更新后的整个SignalChipControl数组通过CCB总线发送给LC75342。VolumeAndToneControl.write(LC75342_ADDR, SignalChipControl, 5);避坑指南LC75342的音量控制曲线是每步1dB但它的控制数据位并不是简单的二进制递增。需要仔细查看数据手册中的“Volume Control Code Table”将期望的dB值转换为正确的位模式。例如-12dB对应的控制码可能不是简单的12。我的代码中VolumeControl函数接受一个0-80的线性值内部需要做一个查表转换这里为了简洁直接使用了位映射在实际应用中需要根据听感或手册进行校准。4. 系统集成与主控程序设计当每个芯片的底层驱动都调试通过后下一步就是将它们整合到一个协调的系统里并设计用户交互。主程序运行在Arduino Nano上需要处理红外遥控、旋转编码器输入并相应地更新PLL频率、LCD显示、音频控制状态。4.1 多任务调度与状态管理Arduino是单线程的所以我们需要在loop()函数中高效地轮询各种输入并管理好各个子系统的状态。全局状态变量定义关键的状态变量如当前波段FM/AM、频率、音量、输入源、静音状态等。uint16_t FMFrequency 949; // 94.9 MHz * 10 uint16_t AMFrequency 53; // 530 kHz / 10 uint8_t currentVolume 48; // 假设的线性音量值 uint8_t currentInput 1; // 1收音机, 2AUX, ... bool isMuted false;红外遥控解码使用经典的IRremote库。在setup()中初始化红外接收在loop()中持续解码。将遥控器上每个按键的编码值通过串口监视器读取映射到具体的功能。if (irrecv.decode(results)) { switch (results.value) { case 0xE0E040BF: // 假设是“音量”键的编码 if (currentVolume 80) currentVolume; VolumeControl(currentVolume); updateDisplayVolume(currentVolume); // 更新LCD或I2C屏显示 break; case 0xE0E000FF: // “频道”键 if (currentBand FM) FMFrequency 1; // 步进0.1MHz LC72131Tune(FMFrequency); updateDisplayFrequency(FMFrequency); break; // ... 其他按键 ... } irrecv.resume(); // 准备接收下一个信号 }旋转编码器处理用于频率微调或音量调节。使用状态检测法而非简单的中断来读取编码器方向。int clkState digitalRead(CLK_PIN); if (clkState ! lastClkState) { if (clkState ! digitalRead(DT_PIN)) { // 逆时针旋转 FMFrequency--; } else { // 顺时针旋转 FMFrequency; } LC72131Tune(FMFrequency); updateDisplayFrequency(FMFrequency); } lastClkState clkState;4.2 用户界面与反馈设计用户需要知道系统当前的状态。我们有两套显示老式CCB驱动的段码LCD和新增的I2C字符LCD。CCB LCD显示核心信息这块屏用于显示最核心、最常查看的信息——电台频率。我们需要将计算好的频率数值通过前面实现的段码映射函数转换成56位数据流发送给LC75821E。例如显示“94.9 FM”。I2C LCD显示辅助信息1602液晶屏用来显示更丰富的状态信息如当前音量大小、输入源、高低音设置等。这比在段码屏上用有限的图标表示要直观得多。lcd.setCursor(0, 0); lcd.print(FM:); lcd.print((float)FMFrequency / 10, 1); // 显示94.9 lcd.print(MHz); lcd.setCursor(0, 1); lcd.print(Vol:); lcd.print(currentVolume); lcd.print( In:); lcd.print(currentInput);音频反馈在切换电台、输入源或静音时可以通过LC75342的静音功能或者短暂地切断音频路径来避免产生“噗噗”的切换噪声。好的用户体验在于这些细节。4.3 电源管理与抗干扰考虑系统包含数字和模拟部分良好的电源设计至关重要。电源去耦在每个芯片的电源引脚附近特别是LC72131PLL对噪声敏感、LC75342音频芯片和TPA3101D2功放旁边必须放置一个0.1uF的陶瓷电容和一个10uF的电解电容以滤除高频和低频噪声。地线布局采用“星型接地”或单点接地原则。将数字地Arduino、CCB芯片和模拟地音频输入、LC75342、功放在一点连接通常是电源输入处。避免数字电流的噪声串入敏感的模拟地线。CCB总线布线虽然频率不高但CLK和DI线应尽量短并远离模拟音频线。如果布线较长可以考虑在Arduino输出端串联一个几十欧姆的电阻以减缓边沿减少振铃和辐射。功放输入从LC75342到TPA3101D2的音频输入线应使用屏蔽线屏蔽层单端接地接模拟地。如果功放有“待机”或“静音”引脚可以由Arduino控制实现上电静音防止开机冲击。5. 调试、问题排查与性能优化实录将这么多老芯片和新控制器整合调试过程就是与各种“幽灵问题”斗争的过程。以下是几个我遇到的典型问题及解决方法。5.1 CCB通信失败无响应或数据错误这是最常见的问题。现象是LCD不显示、收音机不调台、音量控制无效。排查步骤1检查物理连接。用万用表蜂鸣档确保从Arduino引脚到芯片引脚每一根线都连通没有虚焊或短路。特别注意CE引脚是否接对。排查步骤2验证电源和电平。用示波器或逻辑分析仪没有的话用Arduino另一个引脚接LED做简单探测检查CE、CL、DI线上是否有信号。确认信号幅度是否达到芯片要求的电平通常5V CMOS电平。CE信号是否在发送数据期间有正确的拉高拉低排查步骤3检查时序。CCB对时序有要求特别是CL上升沿相对于DI数据的建立时间和保持时间。如果使用SanyoCCB库其write函数内部的digitalWrite和delayMicroseconds构成了基本时序。如果通信不稳定可以尝试微调这些延时。用逻辑分析仪抓取波形与数据手册中的时序图对比是最有效的调试方法。排查步骤4确认芯片地址。每个CCB芯片都有一个硬编码的地址。SanyoCCB.write()的第一个参数就是地址。必须确保这个地址与芯片的A0-A3有时还有B0-B3引脚的上拉/下拉状态匹配。例如LC75342的地址可能是0x82二进制10000010这需要根据其引脚连接通常是接地或接VCC来确定。地址错误芯片根本不会响应。5.2 LCD显示乱码或部分段不亮问题显示的数字残缺或出现不该亮的段。原因1段码映射错误。这是最大的可能。回头检查Digit_1、Digit_2等函数中的bitWrite语句确认每个段a-g对应的Data数组下标和位序号是否正确。最笨但最有效的方法就是回到“暴力测试”阶段逐个位点亮验证。原因2驱动极性错误。LCD有共阴/共阳之分驱动芯片输出也有正逻辑/负逻辑。如果本该点亮的段是0你写了1就会不亮。尝试将所有bitWrite中的1和0对调。原因3初始化或使能信号问题。LC75821E有一个INH抑制引脚。这个引脚的电平状态决定了输出是否驱动LCD。确保在发送显示数据后将INH设置为正确的电平在我的代码中是digitalWrite(INH, HIGH)。5.3 收音机调谐不准或锁不住台问题频率显示变化但实际收不到台或者台漂移。原因1中频IF参数错误。在LC72131Tune函数中FM计算是fpd (frequency 107)这里的107代表10.7MHz中频。如果你的收音头模块中频是10.7MHz吗有些老设备可能是10.6MHz或10.8MHz。AM的中频通常是450kHz或455kHz。这个参数必须准确。原因2参考频率和步进设置错误。LC72131的参考频率由外部晶体如4.5MHz或7.2MHz和内部参考分频器R0-R3决定。这决定了频率步进如FM 50kHz, 100kHz。必须确保LC72131SetMode函数中对于FM/AM波段的R0-R3、DVS、SNS等位的设置与你的晶体频率和期望的步进匹配。计算错误会导致PLL无法锁定在正确的频率上。原因3VCO调谐电压问题。LC72131输出的是调谐电压需要外接一个由运放等构成的有源低通滤波器环路滤波器来驱动变容二极管。这个滤波器的带宽和稳定性直接影响锁相环的锁定速度和稳定性。如果电路设计不佳或元件参数不对会导致锁相环响应慢、噪声大甚至无法锁定。检查收音头模块上的这个滤波电路。5.4 音频控制有噪声或音量调节不平滑问题调节音量时出现“咔嗒”声或者音量变化不是线性感。原因1LC75342的切换噪声。芯片内部的电子开关在切换电阻网络时会产生瞬态噪声。数据手册通常会建议在控制信号变化时先将芯片静音改变设置后再取消静音。确保你的VolumeControl、TrebleControl等函数在修改SignalChipControl数组后一次性发送所有数据而不是分多次发送。频繁的局部更新会导致更多的开关噪声。原因2控制数据格式错误。音量控制码不是简单的线性值。如前所述需要根据数据手册的表格进行映射。错误的码值可能导致音量跳变或产生失真。原因3电源噪声。模拟音频部分对电源噪声极其敏感。确保LC75342和TPA3101D2的电源经过了良好的滤波。模拟部分的电源最好能从总电源处单独经过一个LC滤波器或线性稳压器如78L05获得。5.5 系统整体稳定性优化代码结构将每个芯片的驱动封装成独立的.h和.cpp文件如SanyoRadioPLL.h,SanyoLCD.h,LC75342.h使主程序main.ino清晰简洁。这有利于调试和维护。增加看门狗Arduino Nano有硬件看门狗。在setup()中启用wdt_enable(WDTO_2S)并在loop()中定期wdt_reset()。这样如果程序跑飞系统会自动复位而不是死机。参数存储使用Arduino的EEPROM来保存用户最后设置的频率、音量、音调等参数。这样断电再上电后系统可以恢复到之前的状态提升用户体验。#include EEPROM.h void saveSettings() { EEPROM.put(0, FMFrequency); EEPROM.put(2, currentVolume); // ... } void loadSettings() { EEPROM.get(0, FMFrequency); // ... }通过以上系统的搭建、编程和调试这台由“电子垃圾”拼凑而成的复古音频控制系统最终成功运行。它不仅让我找回了老硬件技术的精髓更打造出了一台独一无二、充满个性且完全可控的音响设备。当你用红外遥控切换着来自三十年前的收音机电台同时通过旋转编码器微调着由老芯片控制的音色时那种跨越时间的工程乐趣正是硬件DIY最大的魅力所在。