在 ATtiny4 和三美分的 Padauk 上制作芯片音乐 (Chiptune)
当硬件限制激发创新解决方案
应广 Padauk 代理:PMS150C 现货 支持芯片音乐 / 低功耗方案
Padauk 代理商:3 美分 PMS150C 全系列硬核应用技术支持
应广单片机代理:PMS150C 现货 芯片音乐方案落地支持

ATtiny4 电路板、内部组装结构和成品设备
当我听到 Rob Miles 的《C小调位移变奏曲》(Bitshift Variations in C Minor)——一段16分钟的4声部复调音频作品时——我非常渴望在硬件上实现它。在任何微控制器上实现这个都太简单了,所以我决定使用我能找到的最小的芯片——ATtiny4。不久之后,我将这个程序移植到了那款著名的三美分微控制器 Padauk PMS150C 上。

啊对了——它还能完全塞进一个 RCA 插头里,并且能自动检测连接。
- 设备播放音乐的视频演示。
- 来自 35C3 大会闪电演讲的幻灯片。
- ATtiny 和 Padauk 的代码仓库。Padauk PMS150C
工作原理
整体功能
在外部层面,该设备实现了两个功能:播放音乐和检测连接状态,以便在离线模式下工作时积累能量。
音乐生成
ATtiny 拥有一个相当强大的定时器/计数器,它承担着双重任务:既为音频输出生成 PWM 信号,又为生成下一个 PCM 采样提供中断。
更准确地说,我们将其配置为 8位非反相 PWM 模式,不使用预分频器,并启用溢出中断。这意味着:
- 定时器从 0 计数到 255。
- 使用的时钟频率与 CPU 内核相同,为 4MHz。
- PWM 输出从低电平开始,在达到设定的占空比值时变为高电平。
- 当计数值达到最大值 (TOP=255) 时,定时器复位为 0,并触发溢出中断。
快速计算如下:
- 微控制器在 3V 纽扣电池供电下能够运行的最高频率是 4MHz。
- 将这 4MHz 除以 256 个计数步骤,得到 PWM 的基础频率为 15.625KHz。
- 稍微校准内部振荡器后,我们可以将其调整到大约 16KHz。
- 原始音调的采样率为 8KHz,这意味着新的采样值只需要每隔两次溢出/中断生成一次。
- 事实证明这非常方便,因为生成一个采样最终只需要略多于 400 个时钟周期。

描述波形图
描述波形图:
在这张波形图上,我展示了:
- 计数器 及其计数值
- 用于比较的值 (可能与比较匹配寄存器相关)
- 最终的 PWM 输出
- 溢出中断信号
- 占空比数值的更新 / 执行采样子程序这个更新/执行动作是由该溢出中断触发的。
描述实际测量:
下方您看到的是实际芯片上的占空比数值和 PWM 输出。这里清晰地显示出,在一个中断周期内生成了两个采样。(并且这个占空比/调试输出之后也派上了用场,因为它允许我通过在 Rigol 示波器上跟踪频率来校准振荡器)。

连接检测
设计机械结构时,我意识到无法在壳体内安装物理按键来停止音乐播放(更不用说带锁定的开关了)。这就是设备自动检测连接这个想法的由来。理论上很简单:当设备插入音频接收器时,音频输出端和地线之间会形成电气连接。我们需要做的就是定期向 RCA 插头的中心触点(信号端)输出高电平,并检测它是否被拉低。
在软件层面,我们记录当前播放到作品的哪个位置,然后进入超低功耗模式。该模式会关闭定时器/计数器,甚至关闭时钟振荡器。在这种状态下,我们等待一个低电平(零电压)外部中断,该中断会在设备被插入时触发,然后恢复播放。
然而,实践中更复杂:典型线路音频输入的阻抗在 20-100kΩ 之间,远高于 ATtiny 内部上拉电阻的阻值(虽然这可以通过外部上拉电阻轻松解决)。此外,PWM 输出引脚不支持引脚电平变化中断功能,但这也容易解决——只需将相关引脚短路连接即可(注:可能指将中断检测引脚与PWM输出引脚物理连接,通过外部上拉实现检测)。
根据规格书,在这种(休眠)状态下的功耗低于 0.15μA。纽扣电池的自放电速率也大致在这个水平,因此电池应该可以持续使用数年。
输出滤波
使用 16kHz 的 PWM 基频有一个缺点:它是可听见的。起初我打算忽略它,但一位朋友说服了我,很有道理地建议我添加一个 RC 低通滤波器。
用手机进行的快速粗略频谱分析证实,这个频率确实会产生可闻噪声。我相当随意地(看着 SMD 元件盒)选择了截止频率约为 8kHz 的滤波曲线。尽管如此,它完美地完成了任务,因此在订购最终的材料清单(BOM)时,我坚持使用了这个方案。

添加滤波器后,信噪比显著提升。
软件开发
搞清楚了理论部分后,剩下的就是编写代码了。我决定手动将 Rob 的 C 程序移植到 AVR 汇编语言上,一方面是出于乐趣,另一方面也是作为一种(或许仓促的?)优化策略。ATtiny 没有硬件乘法/除法/取模指令 (mul/div/mod
),而我只需要少量针对右操作数为常数的乘除法运算,为此我编写了几个专门的、优化过的版本。
我是这样进行的:
- 起点: 从未经修改的 C 程序开始。
- 简化: 首先对它进行简化。
- 替换为汇编宏: 之后,将每一个操作替换成一个 C 宏,该宏实现了对应的机器码指令。
- 严格验证: 每做一点微小的改动,我都会生成 PCM 音频流,并将其与已知正确的参考输出进行比较,以确保没有引入错误。
- 版本控制: 每一次更改都自动提交到代码仓库,最终产生了 136 个名为 "new version" 的提交。
- 添加初始化和上机测试: 只有完成上述步骤后,我才添加了初始化代码,并在真实的微控制器上运行。
调试插曲:
在这个阶段,我毫不知情地在编写其中一个伪汇编宏时犯了一个错误:我在 mod3
运算中反转了条件跳转指令,导致它在不该跳转时跳转,反之亦然。这造成的结果是,在微控制器上无法识别第 3 和第 4 个声道的声音。
我在一年后才找到这个错误的原因。当时我重新拾起这个项目,因为 simavr
模拟器终于添加了对 ATtiny 10 系列的基本支持。当我启动 gdb(1)
进行调试时,问题立刻变得显而易见,修复它只需要一条机器码指令的补丁。
柔性电路板

可缠绕在电池上的柔性印刷电路板
最初,我尝试用漆包线进行两点布线。这个尝试彻底失败了:焊锡无法附着在电池上,而使用导电胶的替代方案则效果脏乱且不牢固。当我尝试用多层聚酰亚胺胶带(高温胶带) 来绝缘电池和元件时,成品体积过大,无法装入插头外壳。
柔性印刷电路板 (FPC) 一举解决了所有这些问题。我设计的电路板可以包裹住纽扣电池,使电池触点位于内侧,而电子元件位于外侧。一个用回形针改造成的夹子将这个"三明治"结构压紧,从而提供电力。电路板本身也起到了绝缘电池的作用,只需用一圈胶带固定即可,并且使整个微型组件足够坚固。
然而,还需要从电路板引出两条导线,焊接到RCA插头内侧的焊点上。这个任务相当棘手,我一开始没想明白如何在不扭绞导线的情况下旋紧插头的两个外壳部分。解决方案是:尽可能深地将电路板推入插头外壳,并在关闭外壳前,将导线逆时针轻微扭转。这样,在旋紧外壳时,导线会反向旋转松开(避免过度扭绞)。


在 KiCad 中设计的柔性电路板的层叠结构,并生成了 PDF 格式的原理图供参考
移植到 Padauk
使用 ATtiny 时,我总有种作弊的感觉:它配备了相对丰富的外设,并且拥有许多(16个)非常灵活的寄存器,可以直接操作它们。此外,我手头正好有一个自制的 Padauk 微控制器编程器(这是 EEVBlog 论坛的一位网友帮我调整好的),还有大约 500 片 PMS150C 芯片。
这些微控制器以其极低的成本而闻名——即使在相对较小的采购量下,每片价格也仅约 3 美分。就其价格而言,它们的配置还算不错:1024 字的一次性可编程只读存储器 (OTP ROM),64 字节的静态随机存取存储器 (SRAM),一个带 PWM 的 8 位定时器,一个(有点怪异的)16 位定时器,一个内部比较器和一个参考电压源。一些人认为 Padauk 的指令集很大程度上沿用了更老的 PIC 模型,并且其大多数操作都发生在一个累加寄存器中。
然而,缺点在于制造商在文档上很吝啬:规格书没有描述汇编助记符如何映射到实际的 1 和 0(即机器码),这意味着你必须使用他们专有的(且仅支持 Windows 的)集成开发环境 (IDE)、编程器和在线仿真器 (ICE)。这个 IDE 甚至没有 C 编译器,而是使用一种他们称之为 "迷你C" 的奇怪语言:本质上,这是一种披着 C 语言语法糖外衣的汇编语言。
令人钦佩的开源努力:
一群才华横溢的爱好者,由 js_12345678_55AA、tim_ (cpldcpu) 和 spth (pkk) 带头,在没有官方支持的情况下,创建了一个令人印象深刻且完全开源的 C 语言工具链。这包括:
- 编译器 (基于 SDCC)
- 汇编器
- 链接器
- 反汇编器
- 模拟器
- 编程器的软件和硬件
- 底层文档

Padauk 版本的内部组装
为了将 Chiptune 移植到 PMS150C 上,需要将原始的 C 代码完全转换为汇编语言,以最好地满足极其严苛的周期数要求(我勉强达标:在最坏的情况下,使用了 512 个可用周期中的 507 个)。在摸索正确初始化外设方法的过程中,我因测试程序烧写报废了 5 片芯片,之后又用了仅 2 片就实现了程序的全面调试。
总共用了 7 片芯片,但实际尝试次数要多得多:实际上,一次性可编程 (OTP) 存储器在仅需将"1"改为"0" 的情况下,是可以多次编程的。因此,我预留了一些空间给复位和中断向量,填入新版本的代码,并"修复"了出错的 GOTO
指令——用 NOP
覆盖旧指令,并紧接着添加一个新的跳转指令。老实说,我并非如此吝啬(指芯片),但反复在 ZIF 插座(零插拔力插座)上折腾所花的时间,会比采用这种变通方法多得多。
微控制器规格对比 | |
---|---|
ATtiny4 微控制器
512字节闪存,32字节SRAM 4MHz工作频率 6引脚SOT-23封装 价格 $0.50左右
功耗 0.15μA (睡眠)
|
应广科技Padauk 单片机 PMS150C
1024字OTP存储器,64字节SRAM 8位和16位定时器 8引脚SOP封装 价格 $0.03 (三美分)
功耗 极低
开发环境 开源工具链
|
Padauk版本与ATtiny版本的一些区别:从内存加载音符
Padauk版本与ATtiny版本相比有一些小的区别:首先,这里我同时使用了两个定时器,这使得可以采用更高的PWM基频(64kHz)并且无需低通滤波器(LPF)。其次,Padauk的内部上拉电阻阻值已经足够高,因此不再需要外部上拉。这意味着我成功实现了完全无需外部元件的设计。
然而,并非没有遇到困难:指令 t1sn M.n
(测试静态RAM中第n位是否为1并跳过下一条指令)和 set1 M.n
(设置静态RAM中的第n位)仅在前16个地址有效;关于这一点,规格书中基本没有明确说明(我注意到这点是因为在逆向工程得到的指令集文档中,相关指令的地址字段是4位的)。在ucism微控制器模拟器中,存在一些与这些(以及类似的)指令相关的错误,这稍微耽误了我的时间(我已将补丁发送到邮件列表)。
此外,timer16的中断模式和休眠模式也表现出奇怪的行为,在JS的帮助下我搞清楚了这个问题。除此之外,整个过程相当顺利,这都要归功于上述各位工程师扎实的工作

请注意更高的PWM频率以及与ATtiny版本相比更高的CPU占用率。
我没有费心去制作新的印刷电路板;我手头还有一些不带低通滤波器(LPF)的版本,我就直接把外部上拉电阻悬空(不连接)了。
现场演示

一段完整播放音乐的视频。开头部分稍长,但从 1:35 开始变得更有趣。
我认为有必要提及 DoJoe 的 Noiseplug 项目。我在查找 ATtiny 相关信息时偶然发现了它,可以说它在本质上与我的项目很相似。
我用转接板为自己制作了一块原型板,因为其焊盘间距正好与 SOT23-6 封装的一侧相匹配。另一侧我之后用导线连接。在另一个更早的原型板版本中,我使用了一块微型的 ATtiny 转接板,我把它粘在了一个通用转接板面板上以扩大其尺寸。最后这个版本我在 35C3(第 35 届混沌通信大会)上展示过