The idea點子Run the decay backwards把衰減倒著跑
A capacitor discharging through a leaky junction is a clock. The open-bus byte on the NES data bus is exactly that clock: it starts full, and the leakage current bleeds it down to 0 in a time that depends — steeply — on temperature. The forward experiment reads the clock to model an emulator. The backward experiment reads the same clock to measure the room: write a known byte, poll it, count how many CPU cycles pass before it decays, and invert the decay law to get the temperature.
一顆電容經一個會漏的接面放電,就是一個時鐘。NES 資料匯流排上的 open-bus 位元組正是那個時鐘:它從滿的開始,漏電流把它放到 0,所花的時間依賴溫度 —— 而且很陡。正向實驗讀這個時鐘來做模擬器。反向實驗讀同一個時鐘來量房間:寫一個已知位元組、輪詢它、數過了多少 CPU cycle 它才衰減,然後把衰減律反過來解出溫度。
Is that real, or is the swing too small to see? The sensitivity settles it. With t ∝ exp(Ea/kT) and Ea ≈ 0.56 eV, the fractional change of decay time with temperature near room temperature is:
這是真的嗎,還是擺幅太小看不到?靈敏度定案。用 t ∝ exp(Ea/kT)、Ea ≈ 0.56 eV,室溫附近衰減時間隨溫度的分數變化是:
7.2% per degree is enormous for a sensor. A cycle-accurate 6502 loop measures the decay time to a handful of parts per million, so temperature quantisation is nowhere near the limit — you could resolve thousandths of a degree in principle. The real limits are electrical noise and, as we'll see, the console cooking itself. But the signal is unambiguously there.
每度 7.2% 對一個感測器來說大得驚人。一個 cycle-accurate 的 6502 迴圈能把衰減時間量到百萬分之幾,所以溫度量化離極限遠得很 —— 原則上你能解到千分之一度。真正的極限是電氣雜訊,以及我們接下來會看到的:主機把自己煮熟。但訊號明確地在那裡。
The one non-obvious trick那個不明顯的訣竅You cannot use the CPU's own bus你不能用 CPU 自己的匯流排
Here is the trap that sinks the naive version. If you write 0xFF to a CPU open-bus address and then run a loop to poll it, you never see it decay. The key insight (corrected by a NESdev reviewer — see the note at the end): "open bus" IS the data bus itself, not storage that is separate from it. The polling instructions have to travel over that same bus, so every fetch drives its own opcode and operand bytes onto the pins, overwriting whatever you wrote — you would be reading the last opcode, not your decaying byte. (That also, incidentally, refreshes the bus and staves off decay, but the operative reason is the overwrite.)
這是讓天真版本沉船的陷阱。如果你寫 0xFF 到一個 CPU open-bus 位址、然後跑迴圈輪詢它,你永遠看不到它衰減。關鍵洞見(經一位 NESdev 審閱者更正 —— 見文末說明):「open bus」就是資料匯流排本身,不是跟它分開的儲存。輪詢用的指令必須走同一條匯流排,所以每次抓取都把自己的 opcode 與運算元位元組驅上接腳,把你寫的東西整個覆蓋掉 —— 你讀到的是最後那個 opcode,不是你那個衰減中的位元組。(這順帶也刷新了匯流排、延後衰減,但真正的原因是覆寫。)
The way out is to watch a bus the CPU's instruction fetches do not touch: the PPU's internal I/O latch. Writing any PPU register ($2000–$2007) fills a parasitic capacitance inside the 2C02. The idiomatic way to seed and read it: write $2002 (PPUSTATUS is read-only, so the write is ignored as a register op but still fills the latch — with no side effect), then read a write-only register such as $2001, which the PPU doesn't drive, so you get the full 8-bit decaying latch. (You can read $2002 instead, but the PPU drives its top 3 status bits, so you'd only see the low 5 bits decay — and you'd have to mask them.) Because that latch is on the PPU die, the 6502's opcode fetches never disturb it. So the procedure is:
出路是去看一條 CPU 指令抓取不會碰到的匯流排:PPU 的內部 I/O 閂鎖。寫任何 PPU 暫存器($2000–$2007)會填滿 2C02 內部一顆寄生電容。慣例的 seed + 讀法:寫 $2002(PPUSTATUS 是唯讀,所以寫入被當暫存器操作忽略、但仍填進閂鎖 —— 而且無副作用),再讀一個寫入專用暫存器(如 $2001),PPU 不驅動它,於是你拿到完整 8 位的衰減閂鎖。(你也可以改讀 $2002,但 PPU 會驅動它高 3 個狀態位元,所以只看得到低 5 位衰減、還得遮罩。)因為那個閂鎖在 PPU 晶粒上,6502 抓 opcode 從不擾動它。所以流程是:
$2002 to seed it (no side effect), read a write-only register ($2001) for the full 8 bits — the one place the CPU's own instruction traffic can't overwrite.量測活在 PPU 的內部 open-bus 閂鎖上:寫 $2002 seed(無副作用)、讀寫入專用暫存器($2001)取完整 8 位 —— 唯一 CPU 自己的指令流量覆寫不到的地方。Calibration — one point is enough校準 —— 一點就夠The slope is a law of physics斜率是物理定律
The absolute decay time is not universal — it hides a pre-exponential factor A that contains this console's exact bus capacitance and junction area, and those vary wildly between motherboard revisions, chip fabs, even individual pins. So a raw decay reading cannot be an absolute temperature on its own. But here is the beautiful part: the slope of the log-curve is the activation energy Ea ≈ 0.56 eV, which is a property of the silicon crystal lattice — virtually identical across every NES. Only the offset A differs per console. And an unknown offset with a known slope needs exactly one calibration point:
絕對衰減時間不普世 —— 它藏著一個指數前因子 A,裝著這台主機確切的匯流排電容與接面面積,那些在主機板版本、晶片廠、甚至個別接腳之間差很大。所以生的衰減讀值本身不能是絕對溫度。但美妙的地方是:log 曲線的斜率是活化能 Ea ≈ 0.56 eV,那是矽晶格的性質 —— 幾乎每台 NES 都相同。只有 offset A 逐台不同。而一個已知斜率、未知 offset 的問題,恰好只需要一個校準點:
// boot in a room known to be 25 °C, press "calibrate", measure t_cal:
ln(A) = ln(tcal) − Ea/(k · 298.15) // solves this console's unique offset
// from then on, temperature is a direct read:
T = (Ea/k) / ( ln(t) − ln(A) )
One button press in a room you trust, and the console pins its own curve. No reference thermometer needed after that first anchor.
在一個你信得過的房間按一次鈕,主機就把自己的曲線釘住。那第一個錨定之後,不再需要參考溫度計。
The fatal flaw致命缺陷You built a die thermometer, not a room thermometer你做的是晶粒溫度計,不是室溫計
Here reality bites hard. The junction whose leakage you are measuring is inside the PPU, and the PPU is dissipating about a watt. In a plastic DIP-40 package with a junction-to-air thermal resistance around 40 °C/W, that die climbs 30–40 °C above the room after ten minutes of play. So the thing your "thermometer" actually measures is the die temperature, not the air in the room. Read it after a session of Super Mario Bros. 3 and it will proudly report a tropical 65 °C.
現實在這裡狠狠咬一口。你在量漏電的那個接面在 PPU 內部,而 PPU 正耗散大約一瓦。在一個塑膠 DIP-40 封裝、接面到空氣熱阻約 40 °C/W 的情況下,那顆晶粒在玩十分鐘後爬到比室溫高 30–40 °C。所以你的「溫度計」實際量的是晶粒溫度,不是房間的空氣。玩完一局《超級瑪利歐 3》再讀它,它會驕傲地報一個熱帶的 65 °C。
To get the room, you have to catch the die before it warms: cold-boot the console after it has been off for an hour, and take the reading within the first few seconds. Two smaller confounds then set the accuracy floor: a sloppy 7805 regulator drifting ±5% shifts the logic threshold ΔV and adds ±1 °C; and part-to-part aging drifts A slowly over decades. Net: at a genuine cold boot, right after a one-point calibration, ±1 °C is realistic. Warm, it's a die sensor with far better relative resolution than absolute accuracy.
要拿到房間,你得在晶粒變熱前抓到它:把主機關一小時後冷開機,在最初幾秒內讀取。接著兩個較小的干擾定出精度地板:一個漂 ±5% 的爛 7805 穩壓器會移動邏輯閾值 ΔV、加 ±1 °C;而器件老化在數十年間慢慢漂移 A。合計:在真正的冷開機、剛做完一點校準之後,±1 °C 是實際可達的。熱起來之後,它是一個相對解析度遠好於絕對精度的晶粒感測器。
Prior art — honestly相關工作 —— 誠實地說The principle is textbook; the carrier is the new part原理是教科書;新的是那個載體
To be honest about what is and isn't new: "a leaking capacitor is a thermometer" is textbook semiconductor physics, and at least one branch — DRAM retention — has been published as a real temperature sensor. What we could find no precedent for is this carrier (an accidental open-bus parasitic) turned into a working software instrument on a games console. Here is the honest lineage, closest first.
誠實說清楚什麼新、什麼不新:「一顆會漏的電容就是溫度計」是教科書半導體物理,而其中至少一支 —— DRAM retention —— 已經被當成真的溫度感測器發表過。我們找不到前例的,是這個載體(一個意外形成的 open-bus 寄生電容)被變成遊戲主機上一把能用的軟體儀器。以下是誠實的族譜,由近而遠。
- ⭐ The circuit is identical: the reverse-biased LED / diode discharge thermometer. A classic Arduino/PIC hack reverse-biases an LED (or any diode) so it acts as a tiny capacitor, then times how long a GPIO pin held high takes to decay to logic-0 — the discharge time is set by the junction's leakage current, which is exponential in temperature. That is our idea at the circuit level, exactly: they use an external LED, we use the chip's internal bus parasitic and read it in software. If you cite one precedent, cite this one.
- ⭐ 電路一模一樣:反偏 LED / 二極體放電溫度計。一個經典的 Arduino/PIC hack 把 LED(或任何二極體)反向偏壓、當成一顆微小電容,再量一支拉高的 GPIO 掉到邏輯 0 要多久 —— 放電時間由接面漏電流決定,而漏電流對溫度呈指數。那在電路層面就是我們的點子、一模一樣:他們用外部 LED,我們用晶片內部匯流排的寄生電容、並用軟體讀它。要引一個前例,就引這個。
- DRAM retention as a sensorless thermometer (published). The physics
I ∝ exp(−Ea/kT)is studied hard on DRAM (a cell is a capacitor + transistor). The Cold Boot Attack (Halderman et al., USENIX Security 2008) proved retention is exponential in temperature — chilling RAM to −50 °C stretches it from milliseconds to hours, long enough to dump keys from powered-off memory. And it has been inverted into an actual sensor: Onur Mutlu's group (RAIDR, ISCA 2012, and later sensorless-thermal-management work) disables refresh on a block and counts bit-flips to infer the die temperature. So "read temperature off memory leakage" is genuinely prior art — our carrier is just weirder. - DRAM retention 當無感測器溫度計(已發表)。物理
I ∝ exp(−Ea/kT)在 DRAM 上被研究得很透(一個 cell 本來就是電容 + 電晶體)。Cold Boot Attack(Halderman 等,USENIX Security 2008)證明 retention 對溫度呈指數 —— 把 RAM 冷到 −50 °C 就從毫秒拉到數小時,久到能從斷電記憶體 dump 金鑰。而它也真的被反過來做成感測器:Onur Mutlu 團隊(RAIDR,ISCA 2012,以及後續的 sensorless thermal management)關掉某個 block 的 refresh、數 bit-flip 反推晶粒溫。所以「從記憶體漏電讀溫度」確實是前例 —— 只是我們的載體更怪。 - Correction — modern MCU sensors are not this. It is tempting to say the temperature sensor in your STM32/AVR/ESP32 is a leaking capacitor; it isn't. Those are PTAT / bandgap circuits — two BJTs at different current densities give a ΔVbe strictly proportional to absolute temperature, linear and process-robust. Leakage/sub-threshold-based sensors (the ring-oscillator kind that is our physics) live only in ISSCC/JSSC ultra-low-power research ASICs — implantables, battery-less RFID — not in stock MCUs.
- 更正 —— 現代 MCU 的感測器不是這個。很容易以為你 STM32/AVR/ESP32 裡的溫度感測器是一顆會漏的電容;它不是。那些是 PTAT / bandgap 電路 —— 兩顆不同電流密度的 BJT 給出嚴格正比於絕對溫度的 ΔVbe,線性又抗製程變異。漏電/次臨界式的感測器(那種 ring-oscillator、才是我們的物理)只活在 ISSCC/JSSC 的超低功耗研究 ASIC 裡 —— 植入式、無電池 RFID —— 不在市售 MCU 中。
- The telling contrast — NESdev already measured this, from the other side. The hardware community (lidnariq, Quietust, blargg) has documented the PPU
$2002open-bus decay in exhaustive detail — the low-5-bit behaviour, the ~100–600 ms window, even that it drifts with temperature. But they measured it to write more accurate emulators, treating temperature as a nuisance variable that makes reads unstable. Nobody, as far as we can find, has written a ROM that turns that nuisance the other way round — into a °C read-out. Reading the decay to model an emulator is old; reading it to measure the room is the new move. - 最說明問題的反差 —— NESdev 早就量過,只是從另一邊。硬體社群(lidnariq、Quietust、blargg)把 PPU
$2002open-bus 衰減記錄得鉅細靡遺 —— 低 5 位行為、~100–600ms 窗口,甚至知道它會隨溫度漂。但他們量它是為了寫更準的模擬器,把溫度當成害讀取不穩定的干擾變數。就我們所能查到,沒人寫過一顆 ROM 把那個干擾反過來變成攝氏讀數。讀衰減來做模擬器是舊的;讀它來量房間才是新招。
Net: the physics is 101, the DRAM sensor is published, and the LED trick is the circuit twin. What appears genuinely new is the combination — this accidental carrier, the insight to dodge CPU self-refresh by reading the PPU latch, the self-heating correction, and packaging it as a stock-hardware software instrument.結論:物理是基礎、DRAM 感測器已發表、LED 把戲是電路雙胞胎。真正看起來新的是這個組合 —— 這個意外的載體、用讀 PPU 閂鎖避開 CPU 自我刷新的洞見、自體發熱修正,以及把它包裝成原廠硬體上的軟體儀器。
The ROM那顆 ROMA thermometer in a few hundred bytes幾百位元組的溫度計
Natural logs on a 6502 are miserable, so we linearise around the calibration point. Since the decay halves every ~9.6 °C, temperature is just an offset from the calibration temperature by the base-2 log of the decay ratio:
6502 上算自然對數很痛苦,所以我們在校準點附近線性化。因為衰減每 ~9.6 °C 減半,溫度就是校準溫度加上衰減比的以 2 為底對數的一個 offset:
And log₂ is trivial on a 6502: the integer part is the position of the highest set bit (found by shifting), the fractional part is a linear interpolation of the next few bits. The whole program is a calibrate loop, a measure loop, a divide, a small lookup table, and a decimal print.
而 log₂ 在 6502 上不費力:整數部分是最高設定位的位置(移位找到),小數部分是接下來幾位的線性內插。整個程式就是一個校準迴圈、一個量測迴圈、一次除法、一張小查表、和一次十進位輸出。
; ---- the measurement loop (identical for calibrate and measure) ---- LDA #$FF STA $2002 ; write a read-only reg -> seeds the latch, no side effect LDX #0 LDY #0 ; 16-bit cycle counter in X (lo) / Y (hi) poll: LDA $2001 ; read a write-only reg -> full 8-bit open bus (4 cyc) CMP #$FF ; still all-ones? (no masking needed) (2) BNE done ; first bit dropped → stop (2/3) INX ; (2) BNE poll ; (3) INY JMP poll done: ; X:Y = decay time in loop iterations
Flow: display "PRESS A AT 25 °C" → run the loop, store T_CAL → later, run it again for T_CUR → shift-divide T_CUR / T_CAL into an 8.8 ratio → a 256-byte LUT maps the ratio to ΔT → add to 25, print to the nametable, repeat at 1 Hz. Time the first bit to drop (it isolates the single leakiest of the five lines, the most repeatable event), and average 16 runs to beat down electrical noise.
流程:顯示「PRESS A AT 25 °C」→ 跑迴圈、存 T_CAL → 之後再跑一次得 T_CUR → 移位除 T_CUR / T_CAL 成 8.8 比值 → 一張 256 byte LUT 把比值對到 ΔT → 加到 25、印到 nametable、1Hz 重複。量第一個掉的位元(它隔離出五條線裡最漏的那一條、最可重現的事件),並平均 16 次以壓下電氣雜訊。
Verdict — and try it裁決 —— 試試看Party trick for the room, real sensor for the die室溫是把戲,晶粒是真感測器
As a room thermometer it is a wonderful party trick, honest to about ±1 °C only at a careful cold boot, and useless the moment the console warms itself. But as a PPU die thermometer it is a genuinely legitimate, high-resolution sensor: you could plot the exact thermal time-constant of the Ricoh 2C02 heating up in real time, on a CRT, in a homebrew ROM. The physics that made open-bus a headache for the emulator turns out to be a working instrument. Below, the inverse in your browser — feed it a measured decay time and it reads back the temperature it implies:
當室溫計,它是個美好的派對把戲,只在小心的冷開機下才誠實到約 ±1 °C,主機一暖起來就沒用。但當PPU 晶粒溫度計,它是真正合法、高解析度的感測器:你能在 CRT 上、用自製 ROM,即時畫出 Ricoh 2C02 升溫的確切熱時間常數。那個讓 open-bus 成為模擬器頭痛的物理,原來是一把能用的儀器。下面是瀏覽器裡的反向計算 —— 餵它一個量到的衰減時間,它讀回那意味著的溫度:
Interactive — the thermometer, inverted互動 —— 反向的溫度計
Slide a measured decay time. The console would infer this die temperature from it (one-point-calibrated at 600 ms = 25 °C).拖動一個量到的衰減時間。主機會從它反推這個晶粒溫度(在 600ms = 25 °C 做一點校準)。
T = (Ea/k) / ( ln(t) − ln A ), Ea ≈ 0.56 eV, anchored at t = 600 ms → 25 °C. This is the die, not the room — subtract self-heating for the air.T = (Ea/k) / ( ln(t) − ln A ),Ea ≈ 0.56 eV,錨定在 t = 600ms → 25 °C。這是晶粒、不是房間 —— 要空氣溫度得扣掉自體發熱。
← back to deep dives · the open-bus decay it inverts← 回深入專文 · 它反轉的 open-bus 衰減
Corrections & thanks更正與致謝What a NESdev reviewer got right一位 NESdev 審閱者指對的地方
After publishing, a NESdev community reviewer raised several sharp, correct objections. With thanks, the fixes are folded in above and summarised here:
發表後,一位 NESdev 社群審閱者提出了幾點犀利且正確的質疑。致謝之餘,更正已併入上文,並在此彙整:
- The CPU-bus mechanism. The earlier text said instruction fetches "re-charge the capacitor / refresh the $FF". More precisely: open bus is the bus itself, and the polling opcodes travel over it and overwrite the value — you'd read the last opcode. (Corrected above.)
- CPU-bus 機制。先前寫指令抓取「re-charge 那顆電容 / 刷新 $FF」。更精確地說:open bus 就是匯流排本身,輪詢的 opcode 走過它、把值覆寫掉 —— 你會讀到最後那個 opcode。(已於上文更正。)
- $2002 was backward. The clean idiom is write $2002 (read-only → no side effect) and read a write-only register ($2000/$2001/$2003/$2005/$2006) for the full 8-bit open bus — not write $2003 (OAMADDR side effect) and read $2002 (only 5 bits). (Corrected in the diagrams and code above.)
- $2002 用反了。乾淨的慣例是寫 $2002(唯讀 → 無副作用)+ 讀寫入專用暫存器($2000/$2001/$2003/$2005/$2006)取完整 8 位 open bus —— 而非寫 $2003(OAMADDR 副作用)+ 讀 $2002(只 5 位)。(圖與程式碼已更正。)
- Arrhenius is a first-order model, not the whole story.
Ea ≈ 0.56 eV≈ Eg/2 is the depletion-region generation–recombination current that dominates near room temperature; well above it (≳80–100 °C) diffusion current takes over withEa ≈ Eg ≈ 1.12 eV— a slope kink. And the measured decay time also depends on the junction capacitance's voltage dependenceC(V), the read buffer's threshold voltage drift (~−2 mV/°C), and VDD. The single-Arrhenius fit is a good local model, not a complete one. - Arrhenius 是一階模型、不是全部。
Ea ≈ 0.56 eV≈ Eg/2 是空乏區 generation–recombination 電流,在室溫附近主導;遠高於室溫(≳80–100 °C)擴散電流接手,Ea ≈ Eg ≈ 1.12 eV—— 斜率折點。而量到的衰減時間還取決於接面電容的電壓相依C(V)、讀取緩衝的閾值電壓漂移(~−2 mV/°C)、以及 VDD。單一 Arrhenius 擬合是不錯的局部模型,不是完整模型。 - Calibration has no ground truth. The die self-heats and the NES has no on-chip thermal sensor, so software can never read the true junction temperature to check its own calibration against. External package/ambient temperature is only a proxy (a thermal-gradient error away). This is a fundamental limit, not a tuning problem — the honest framing is a relative instrument, best-effort anchored at a careful cold boot.
- 校準沒有 ground truth。晶粒會自體發熱,而 NES 沒有片上溫度感測器,所以軟體永遠讀不到真實接面溫度去檢驗自己的校準。外部封裝/環境溫度只是 proxy(隔著一層熱梯度誤差)。這是根本限制、不是調校問題 —— 誠實的定位是一把相對儀器,盡力錨在小心的冷開機。
- The emulator "round-trip" is circular. The companion build (deep dive #4) sets decay = f(T) in the emulator and then inverts it — so recovering T proves only that the software math is right, not that the model holds on real silicon. That article now says so explicitly.
- 模擬器「round-trip」是循環的。配套實作(深入專文 #4)在模擬器裡設 decay = f(T) 再反推 —— 所以拿回 T 只證明軟體數學對,不證明模型在真矽晶成立。那篇現在已明講。