Can the NES tell you the room temperature?NES 能告訴你室溫嗎?

Deep dive #3 · a party trick with real physics behind it. The previous article showed that the open-bus byte decays faster when it is warmer — over 100× across a normal room's temperature range. So run the experiment backwards: write a byte, time how long it survives, and read the temperature off the decay clock. No thermistor, no extra hardware — just a stock 6502 and a leaky capacitor. This asks whether it actually works, walks through the one non-obvious trick that makes it possible, the calibration, the fatal flaw that turns it from a room thermometer into a die thermometer, and gives you the ROM.

深入專文 #3 · 一個背後有真物理的派對把戲。前一篇證明 open-bus 位元組在越熱時衰減越快 —— 在一個房間的正常溫度範圍內就差超過 100 倍。那就把實驗反過來跑:寫一個位元組、量它活多久,然後從衰減時鐘讀出溫度。沒有熱敏電阻、沒有額外硬體 —— 只有一顆原廠 6502 和一顆會漏的電容。這篇問它到底行不行、走過讓它成為可能的那個不明顯的訣竅、校準、把它從室溫計變成晶粒溫度計的致命缺陷,並把 ROM 給你。

S1a · deep dive #3the NES as a sensor2026-07-20

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,室溫附近衰減時間隨溫度的分數變化是:

(1/t)(dt/dT) = −Ea / (k T²) = −0.56 / (8.617e−5 · 300²) = −0.072 / °C = −7.2 % per °C

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 從不擾動它。所以流程是:

Measure the PPU internal latch, not the CPU bus write $FF to $2002 → primes PPU latch = $FF poll loop LDA $2001 (write-only reg) CMP #$FF · count loops first bit drops latch ≠ $FF → stop, read counter invert t → T via the calibrated log law ✗ the trap: polling a CPU open-bus address (e.g. $4020) fails — the LDA/CMP/BNE opcodes travel over that same bus, so each fetch OVERWRITES it with its own bytes. You read the last opcode, not the decay. ✓ the PPU internal latch (write $2002, read a write-only reg) is on the PPU die — fetches can't touch it.
The measurement lives on the PPU's internal open-bus latch: write $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 的問題,恰好只需要一個校準點:

ln(t) = ln(A) + Ea/(kT) // the decay law in log form; slope Ea/k is fixed

// 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 是實際可達的。熱起來之後,它是一個相對解析度遠好於絕對精度的晶粒感測器。

The die cooks itself — the reading window is the first few seconds time since cold boot (minutes) die temperature room 25 °C +40 °C 0 5 10 15 20 min die self-heating (τ ≈ a few minutes) read here (first few s, cold boot) "65 °C" after SMB3
The PPU dissipates ~1 W into a DIP-40 (~40 °C/W), so the die runs 30–40 °C hot. The only window where the die ≈ the room is the first few seconds after a cold boot.PPU 把 ~1W 耗散進 DIP-40(~40 °C/W),所以晶粒跑得比室溫高 30–40 °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 寄生電容)被變成遊戲主機上一把能用的軟體儀器。以下是誠實的族譜,由近而遠。

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:

T ≈ 25 °C − 9.6 · log₂( t / t_cal ) // warmer → shorter t → higher T

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 做一點校準)。

111 ms600 ms15 min
measured 600 ms  → die ≈  25.0 °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 社群審閱者提出了幾點犀利且正確的質疑。致謝之餘,更正已併入上文,並在此彙整: