在 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 屆混沌通信大會)上展示過