NES(ファミコン)のノイズジェネレーターを設計しよう 解析・設計編

NESのノイズジェネレーターについて 

NESのノイズは計算で説明がつく構造なのである程度の情報で設計が可能です。
音を作っているのはAPUと呼ばれるメインCPUです。 
まず(横に)15ビットPRBS(LFSR)だそうです。これだけで回路が分かります。  


そして有名なRoland TR-909のノイズジェネレーター回路。
IC3個は驚くしかない。



RolandのTR-909のノイズジェネレーターは(横に)17ビットPRBSで出来ています。PRBSの特徴として波形が方形波なんです。0か1かの時間がバラバラなのです。
なので回路上では17ビットPRBSは17ステージの~と表現します。確かに1ビットなのに17ビットPRBSでは話を難しくするだけです。 
バラバラではありますが論理回路ですからどうしても繰り返しのループが発生してしまいます。ですから何クロック目にはビット列はこうなる。と、計算できてしまうのです。
アーケードゲームでもこのタイミングでスタートするとテトリスのブロックの発生パターンは必ずこうなるという攻略法も存在します。
人間にとっては乱数に見えるだけでコンピューターにとっては小さなアルゴリズムで作る大きな分散された表なのです。
話を戻しましょう。
 

疑似乱数の秘密はフィードバックにあり 

 
色々難しい話になるので簡単に説明します。
まず15ビットの乱数(効率よく分散された表)を計算で作るにはDフリップフロップを連結したビットシフターという特殊な論理回路を使います。15ステージ必要ですが、ただシフトするだけではただの1ビットデジタルディレイですのでExclusiveORという論理回路を2つか4つ使い、途中のステージの出力がどちらも0か1の場合1としてフィードバックします。このフィードバックが疑似乱数を作るのです。
 
 

イメージできます?

下の方でシミュレーションしますけど、1ビット15段デジタルディレイの途中の段の2つを取り出してEx-ORしてフィードバック。
「乱数(効率よく分散された表)」と書いたようにここで作られるのは計算された周期のあるデータになります。 その計算式を実現するのが下の表です。
 
BITSTAPS
33,2
44,3
55,3
66,5
77,6
88,6,5,4
99,5
1010,7
1111,9
1212,6,4,1
1313,4,3,1
1414,5,3,1
1515,14
1616,15,13,4
1717,14
1818,11
1919,6,2,1
2020,17
2121,19
2222,21
2323,18
2424,23,22,17
2525,22
2626,6,2,1
2727,5,2,1
2828,25
2929,27
3030,6,4,1
3131,28
3232,22,2,1

このようにするとうまく分散されるそうです。なぜ?と思う方は説明が長くなるので省略します。 
あれ?TR-909は31ステージで13,31抜取です、後でリンクを張りますがそれでも間違ってはいないそうです。
PSG(SN76489)は16ステージで13,16抜取のようです。これで古いアーケードゲームのノイズもOKですね。
CIDは23ステージで22と17抜取らしいですよ。23と18じゃないの?なんだかどの会社もバラバラですね。
 

いざ、シミュレーションしてみましょう。

 
Resetに使うExORは100kΩによってシュミットトリガーとして使われる特殊な方法


 
リンクはこちら
 
 
右に再生ボタンがありますがシミュレートを進めないとデータも作られないので再生できません。
最高速にするとすぐ再生できます。 6ステージだと音で繰り返しがわかりますね。
 
シュミットトリガー部分はたまたまTR-909の回路を使ったので余裕があるなら100kΩ無しでのバッファー1つやNAND回路2つ(NOT+NOT)で置き換え可能です。 
 
PRBSは何ステージでも1ビット表現ですからTR-909もクロックを440Hzにするとループがわからないファミコンのノイズになります。
(月面着陸に使うスラスター音のような)

とりあえずAPUとPSGのノイズはこんな回路で出来ちゃいます。

音は無加工のまま右に進むので音の取り出し口は1つで問題ありません。ループ音も再現され問題ありません。



リンクはこちら


そしてシフトレジスターって何?1ビットでディレイ作って意味無くね?と思う方はコンピューターのCPUについて学ぶ事になるので省略します。
さらにDフリップフロップって何?甘いの?と思う方はロジックICの種類と記号を学びましょう。回路図を読みたい人は覚えて損はないです。 

 困ったことにシフトレジスターって4ステージ(4ビット)単位なので抜き取りたい段数が4の倍数だけではないのでDフリップフロップと繋げて設計します。
手元にTR-909で使われているCD4006BE(4ステージ,4+1ステージ)x2が沢山ありますのでそれを使います。
細かくステージの出力を抜き取るにはDフリップフロップ(CD40175BEか74HC175)を使います。
全部Dフリップフロップ使ってもいいですよ。ただし面積は4倍以上大きくなります。 

CD4006BEの+1ステージ部分だけDフリップフロップ単独で使えないの?
残念ですが使えません。これがデータシートです。


+1部分のQ(D)に繋がっていれば使えたんですけどね。なので4ステージか5ステージで使うしかありません。
細かく刻むには何も心配しないベーシックなつくりのCD40175BEがおすすめです。

次はクロック部分をAIで生成させましょう。

そしてさらに考えなければいけないのは与えるクロックスピードです。
https://www.nesdev.org/wiki/APU_Noise
NTSC   4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068 (2046 in old)
 
NTSCクロックから分周し、16種類クロックを与えるようです。4068分周は440Hzですね。
はい困りました。
すべてが1/2で出来た世界ではありません。まぁ現代なら小型のMCUを使ってクロック生成してしまいましょう。元々APUの中の出来事です。わざわざロジック回路で作っても面積がもったいないです。
もちろん440Hzから4オクターブの矩形波作ってもいいですよ。
5Vな矩形波のサウンドで良いのです。本物志向から外れますが楽しめます。
 
以下Seeeduino XIAOでのソースです。AIに生成してもらいました。無保証です。
 
 

このコードは、SAMD21マイコンのGCLK (Generic Clock Controller) および TCC (Timer/Counter for Control Applications) という機能を直接利用して、正確な周波数を効率的に生成します。

回路接続

まず、以下のように部品を接続してください。

  • A0ピン: ボリューム(可変抵抗)の真ん中の端子に接続します。ボリュームの両端の端子は、それぞれ 5VGND に接続します。

  • D1ピン: 周波数が可変のクロックが出力されます。

  • D6ピン: 周波数が固定のクロックが出力されます。


Arduino用ソースコード

以下のコードをコピーして、Arduino IDEに貼り付けてXIAO SAMD21に書き込んでください。

/**
 * @file XIAO_SAMD21_Variable_Clock_Generator.ino
 * @author Gemini
 * @brief Seeed Studio XIAO SAMD21で、指定仕様のクロックを生成するコード
 * @version 1.0
 * @date 2025-09-22
 * * @details
 * - D1ピン: A0のAD値に応じて、3.579545 MHzを16段階に分周したクロックを出力
 * - D6ピン: 常に約137kHzのクロックを出力
 * - A0ピン: 分周比を切り替えるためのアナログ入力 (ボリュームを接続)
 */

// --- ピン定義 ---
const int VARIABLE_CLOCK_PIN = D1;  // 可変クロック出力ピン (PA04)
const int ADC_INPUT_PIN = A0;       // AD入力ピン
const int FIXED_CLOCK_PIN = D6;     // 固定クロック出力ピン (PA10)

// --- 定数 ---
const float BASE_FREQ = 3579545.0f; // 基準となる周波数 (Hz)
const uint32_t GCLK0_FREQ = 48000000UL; // SAMD21のメインクロック (48MHz)
const uint32_t FIXED_CLOCK_TARGET_FREQ = 137000UL; // D6から出力する固定クロックの目標周波数 (Hz)

// 16段階の分周比を格納した配列
const uint16_t DIVIDERS[16] = {
  4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068
};

// 前回のADレベルを記憶する変数 (初回更新用)
int last_adc_level = -1;

// --- プロトタイプ宣言 ---
void setupFixedClock();
void setupVariableClock();
void updateVariableClock(int level);

//================================================================
//  セットアップ関数
//================================================================
void setup() {
  // D6ピンに固定周波数クロックを出力する設定
  setupFixedClock();

  // D1ピンに可変周波数クロックを出力する設定
  setupVariableClock();
  
  // A0ピンを入力に設定
  pinMode(ADC_INPUT_PIN, INPUT);

  // 初回実行時にデフォルトのクロック(分周比4)を設定
  updateVariableClock(0); 
}

//================================================================
//  メインループ
//================================================================
void loop() {
  // A0ピンからAD値を取得 (0-1023)
  int adc_value = analogRead(ADC_INPUT_PIN);
  
  // AD値を16段階のレベル (0-15) に変換
  int current_level = map(adc_value, 0, 1023, 0, 15);

  // レベルが変更された場合のみ、クロックの周波数を更新
  if (current_level != last_adc_level) {
    updateVariableClock(current_level);
    last_adc_level = current_level;
  }
  
  // 15ms待機して、処理負荷を軽減
  delay(15);
}

//================================================================
//  D1ピン: 可変クロックの周波数を更新する関数
//================================================================
void updateVariableClock(int level) {
  // 配列から現在の分周比を取得
  uint16_t divider = DIVIDERS[level];
  
  // 目標の周波数を計算
  float target_freq = BASE_FREQ / divider;
  
  // TCC0の周期レジスタに設定する値を計算
  // 周波数 = GCLK周波数 / (PER + 1) => PER = (GCLK周波数 / 周波数) - 1
  uint32_t period_value = (uint32_t)(((float)GCLK0_FREQ / target_freq) - 1.0f);
  
  // TCC0の周期(PER)とデューティ比(CC)を更新
  // バッファレジスタ(PERBUF, CCBUF)を使うことで、出力波形にグリッチが発生するのを防ぎます
  TCC0->PERBUF.reg = period_value;
  TCC0->CCBUF[4].reg = period_value / 2; // デューティ比50%
}

//================================================================
//  D6ピン: 固定クロック(約137kHz)の初期設定
//================================================================
void setupFixedClock() {
  // 1. GCLKジェネレータ5を有効化し、48MHzをソースに設定
  GCLK->GENCTRL.reg = GCLK_GENCTRL_IDC |           // デューティ比を50%に補正
                      GCLK_GENCTRL_GENEN |         // ジェネレータを有効化
                      GCLK_GENCTRL_SRC_DFLL48M |   // ソースとして48MHz DFLLを選択
                      GCLK_GENCTRL_ID(5);          // GCLKジェネレータ5を選択
  while (GCLK->STATUS.bit.SYNCBUSY);               // 同期完了を待つ

  // 2. GCLK5の分周比を設定
  // 48,000,000 Hz / 351 = 136,752 Hz (約137kHz, 誤差 -0.18%)
  GCLK->GENDIV.reg = GCLK_GENDIV_DIV(351) |        // 分周比を351に設定
                     GCLK_GENDIV_ID(5);            // GCLK5に適用
  while (GCLK->STATUS.bit.SYNCBUSY);               // 同期完了を待つ

  // 3. D6ピン(PA10)をGCLK5の出力として設定 (ピンマルチプレクサ機能)
  pinMode(FIXED_CLOCK_PIN, OUTPUT);
  PORT->Group[g_APinDescription[FIXED_CLOCK_PIN].ulPort].PINCFG[g_APinDescription[FIXED_CLOCK_PIN].ulPin].bit.PMUXEN = 1;
  // D6は偶数ピンなのでPMUXE、ペリフェラル機能"H"(7)でGCLK_IO[5]に接続
  PORT->Group[g_APinDescription[FIXED_CLOCK_PIN].ulPort].PMUX[g_APinDescription[FIXED_CLOCK_PIN].ulPin >> 1].reg |= PORT_PMUX_PMUXE_H;
}

//================================================================
//  D1ピン: 可変クロックの初期設定
//================================================================
void setupVariableClock() {
  // 1. TCC0タイマーにクロック(GCLK0=48MHz)を供給
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |         // クロックを有効化
                      GCLK_CLKCTRL_GEN_GCLK0 |     // ソースとしてGCLK0(48MHz)を選択
                      GCLK_CLKCTRL_ID_TCC0_TCC1;   // TCC0とTCC1に供給
  while (GCLK->STATUS.bit.SYNCBUSY);               // 同期完了を待つ

  // 2. D1ピン(PA04)をTCC0の波形出力(WO[4])として設定
  pinMode(VARIABLE_CLOCK_PIN, OUTPUT);
  PORT->Group[g_APinDescription[VARIABLE_CLOCK_PIN].ulPort].PINCFG[g_APinDescription[VARIABLE_CLOCK_PIN].ulPin].bit.PMUXEN = 1;
  // D1は偶数ピンなのでPMUXE、ペリフェラル機能"E"(5)でTCC0/WO[4]に接続
  PORT->Group[g_APinDescription[VARIABLE_CLOCK_PIN].ulPort].PMUX[g_APinDescription[VARIABLE_CLOCK_PIN].ulPin >> 1].reg |= PORT_PMUX_PMUXE_E;
  
  // 3. TCC0タイマーを設定
  TCC0->CTRLA.reg |= TCC_CTRLA_PRESCALER_DIV1;     // プリスケーラ(分周)を1に設定
  
  // 波形生成モードをNormal PWM (NPWM)に設定
  TCC0->WAVE.reg |= TCC_WAVE_WAVEGEN_NPWM;
  while(TCC0->SYNCBUSY.bit.WAVE);                  // 同期完了を待つ

  // 4. TCC0を有効化
  TCC0->CTRLA.reg |= TCC_CTRLA_ENABLE;
  while (TCC0->SYNCBUSY.bit.ENABLE);               // 同期完了を待つ
}
 
 

コードの解説

  • setupFixedClock() 関数:

    • XIAOのメインクロックである48MHzを、GCLK(汎用クロック生成器)を使って 351分周 します。

    • Hz となり、目標の137kHzに対して誤差約-0.18%の安定したクロックを生成します。

    • 生成したクロックをD6ピンに出力するよう、ピンの機能を切り替えています(ピンマルチプレクサ設定)。

  • setupVariableClock() 関数:

    • より高度なPWM生成が可能なTCC(タイマー/カウンター)を使用します。

    • TCC0のクロック源として48MHzを直接入力します。

    • D1ピンをTCC0の波形出力ピンとして設定します。

    • TCC0をPWMモードで有効にします。

  • updateVariableClock(int level) 関数:

    • A0ピンから読み取ったAD値のレベル(0〜15)に応じて、DIVIDERS配列から分周比を取得します。

    • 3.579545 MHz / 分周比 で目標周波数を計算します。

    • その目標周波数を48MHzのクロックから生成するために必要なタイマーの周期 (period_value) を逆算します。

    • 計算した値をTCC0の周期レジスタ(PERBUF)とデューティ比レジスタ(CCBUF)に書き込むことで、出力クロックの周波数が変更されます。

  • loop() 関数:

    • A0ピンの電圧を常に監視し、ボリュームが回されて値が変化した時だけ updateVariableClock 関数を呼び出して周波数を更新します。これにより、無駄な計算を省いています。



以下Raspberry PI PICOのPIOで作ってみます。無保証ですがこちらはかなりお勧めです。


"""
Raspberry Pi Pico (RP2040) MicroPython
--------------------------------------
GP22 (29pin) : 起動直後(初期化)は20ミリ秒間だけ「8分周」のクロックを
               出力し、その後はADC2の値で選んだ分周比
               (4/8/16/32/64/96/128/160/202/254/380/508/762/1016/
                2034/4068) で分周した方形波クロックを出力
GP21 (27pin) : 初期化の有無に関わらず、常時 137kHz 固定クロックを出力
GP19 (25pin) : 常時 769.2307Hz (周期約1.3ms) 固定クロックを出力
               ※CASIO VL-Toneのリズム音源"Po"用クロック
GP18 (24pin) : 常時 1666.6666Hz (周期約0.6ms) 固定クロックを出力
               ※CASIO VL-Toneのリズム音源"Pi"用クロック
GP20 (26pin) : 電源投入(リセット)直後、GP22の初期化出力(8分周)と同時に
               20ミリ秒間だけハイレベルを保持し、その後ローレベルに落とす
ADC2 (34pin = GP28) : 出力する分周比を16段階で選択するアナログ入力

方式:
  PIO ステートマシンで「1周期64クロック(Hi:32 + Lo:32)」の方形波を作り、
  PIOの動作クロック(SMクロック = システムクロック / clkdiv)を
  目的の出力周波数×64 に設定することで任意の周波数を作る。
  (実際に1.7897725MHzのクロックを生成してから分周するのではなく、
   同じ結果になる最終周波数を直接PIOクロックで作る方式。
   RP2040のPIOクロック分周器は16bit整数+8bit小数のため誤差は
   0.01%未満に収まる)

ADC値(0〜65535)を上位4bitで16段階(0〜15)に分割し、
  ADC=0(最小)  -> 4068分周 (最も遅い = 439.96Hz)
  ADC=15(最大) -> 4分周    (最も速い = 447443.13Hz)
となるようにしている(ADCが大きいほど周波数が高い)。
"""

import machine
import rp2
from machine import Pin, ADC
import time

# ---------------------------------------------------------------
# 定数定義
# ---------------------------------------------------------------
BASE_FREQ_HZ   = 1.7897725e6      # 基準クロック 1.7897725MHz
FIXED_FREQ_HZ  = 137_000          # GP21に常時出力する固定クロック
CYCLES_PER_PERIOD = 64            # PIOプログラム1周期あたりのSMクロック数(Hi32+Lo32)

PIN_CLK_OUT   = 22   # GP22 (29pin) 分周クロック出力
PIN_CLK_FIXED = 21   # GP21 (27pin) 137kHz固定出力
PIN_POR_PULSE = 20   # GP20 (26pin) 初期化時 20ms Hiパルス出力
PIN_PO        = 19   # GP19 (25pin) CASIO VL-Tone "Po" 用クロック出力
PIN_PI        = 18   # GP18 (24pin) CASIO VL-Tone "Pi" 用クロック出力
ADC_PIN       = 28   # GP28 (34pin) = ADC2

INIT_DIVIDER    = 8               # GP22の初期化中(20ms)に出力する分周比
INIT_DURATION_MS = 20             # 初期化(GP22=8分周 かつ GP20=Hi)の継続時間

PO_FREQ_HZ = 769.2307             # GP19 "Po" 用クロック周波数(周期約1.3ms)
PI_FREQ_HZ = 1666.6666            # GP18 "Pi" 用クロック周波数(周期約0.6ms)

# ADC値(0〜15)に対応する分周比。index0(ADC最小)が最大の分周比=最も遅い周波数
DIVIDERS = [4068, 2034, 1016, 762, 508, 380, 254, 202,
            160, 128, 96, 64, 32, 16, 8, 4]

assert len(DIVIDERS) == 16

# 各レベルに対応する目的出力周波数、およびPIO(SM)クロック周波数を事前計算
OUT_FREQS = [BASE_FREQ_HZ / d for d in DIVIDERS]
SM_FREQS  = [round(f * CYCLES_PER_PERIOD) for f in OUT_FREQS]

FIXED_SM_FREQ = round(FIXED_FREQ_HZ * CYCLES_PER_PERIOD)
PO_SM_FREQ    = round(PO_FREQ_HZ * CYCLES_PER_PERIOD)   # GP19 "Po"用SM周波数
PI_SM_FREQ    = round(PI_FREQ_HZ * CYCLES_PER_PERIOD)   # GP18 "Pi"用SM周波数


# ---------------------------------------------------------------
# PIO プログラム: 1周期 = 64 SMクロック (Hi 32 + Lo 32) の方形波
# ---------------------------------------------------------------
@rp2.asm_pio(set_init=rp2.PIO.OUT_LOW)
def square_wave():
    set(pins, 1) [31]
    set(pins, 0) [31]


# ---------------------------------------------------------------
# ステートマシン初期化
# ---------------------------------------------------------------
# SM1: GP21 固定137kHzクロック
#   初期化処理の有無に関わらず、常に最優先で出力を開始する。
sm_fixed = rp2.StateMachine(
    1, square_wave, freq=FIXED_SM_FREQ, set_base=Pin(PIN_CLK_FIXED)
)
sm_fixed.active(1)

# SM2: GP19 "Po" 用クロック(769.2307Hz)
#   常時出力。初期化シーケンス(GP22/GP20)とは無関係に動作する。
sm_po = rp2.StateMachine(
    2, square_wave, freq=PO_SM_FREQ, set_base=Pin(PIN_PO)
)
sm_po.active(1)

# SM3: GP18 "Pi" 用クロック(1666.6666Hz)
#   常時出力。初期化シーケンス(GP22/GP20)とは無関係に動作する。
sm_pi = rp2.StateMachine(
    3, square_wave, freq=PI_SM_FREQ, set_base=Pin(PIN_PI)
)
sm_pi.active(1)

# SM0: GP22 可変分周クロック
#   初期化として、まず「8分周」の周波数で出力を開始する。
INIT_SM_FREQ = round((BASE_FREQ_HZ / INIT_DIVIDER) * CYCLES_PER_PERIOD)

sm_var = rp2.StateMachine(
    0, square_wave, freq=INIT_SM_FREQ, set_base=Pin(PIN_CLK_OUT)
)
sm_var.active(1)

# GP22の初期化出力(8分周)と同時にGP20をHiにし、INIT_DURATION_MSだけ保持する
_por_pin = Pin(PIN_POR_PULSE, Pin.OUT, value=1)
time.sleep_ms(INIT_DURATION_MS)
_por_pin.value(0)

# ---------------------------------------------------------------
# ADC2 読み取り設定
# ---------------------------------------------------------------
adc2 = ADC(Pin(ADC_PIN))


def read_level():
    """ADC2の値(0-65535)を16段階(0-15)に変換する"""
    val = adc2.read_u16()
    level = val >> 12   # 65536 / 16 = 4096 = 2^12
    if level > 15:
        level = 15
    return level


# ---------------------------------------------------------------
# メインループ
#   ADC値を定期的に読み取り、レベルが変化したらGP22の出力周波数を
#   更新する。チャタリング防止のため、2回連続で同じレベルを
#   読んだ場合のみ切り替える。
# ---------------------------------------------------------------
def main():
    current_level = -1
    pending_level = -1
    pending_count = 0

    while True:
        level = read_level()

        if level == pending_level:
            pending_count += 1
        else:
            pending_level = level
            pending_count = 1

        # 2回連続で同じ値を検出したら確定
        if pending_count >= 2 and level != current_level:
            current_level = level
            new_freq = SM_FREQS[current_level]

            # PIOステートマシンのクロック周波数を再設定
            # (init()を再度呼び出すことでPIOクロック分周器のみ変更)
            sm_var.active(0)
            sm_var.init(square_wave, freq=new_freq, set_base=Pin(PIN_CLK_OUT))
            sm_var.active(1)

            print(
                "ADC level={:2d}  divider={:5d}  freq={:10.2f}Hz".format(
                    current_level,
                    DIVIDERS[current_level],
                    OUT_FREQS[current_level],
                )
            )

        time.sleep_ms(50)


if __name__ == "__main__":
    main()


    GP28(ADC入力)

    • 3.3V16段階。
    • 初期化が終わった後に有効となる。といっても20mSです。

    GP22(分周選択出力)

    • 16段階の分周比: 4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068
    • ADC2(GP28/34pin)の値(0〜65535)を上位4bitで16段階に変換
    • ADC値0 → 4068分周(439.96Hz、最も遅い)
    • ADC値15段階目(最大)→ 4分周(447443.13Hz、最も速い)
    • 2回連続で同じレベルを検出したときのみ切り替え(チャタリング防止)
    • 初期化用に起動時20mSの間は分周比8を出力。その間GP20(26pin)の信号を初期値として入力するとノイズが生成される。

    GP21(固定出力)

    • 常時137kHz固定(起動後は一切変更しない別のPIOステートマシンで駆動)
    • 初期化用に起動時20mSはGP20(26pin)の信号を初期値として入力するとノイズが生成される。

    主な周波数一覧:

    分周比出力周波数
    4447443.13 Hz
    8223721.56 Hz
    16111860.78 Hz
    3255930.39 Hz
    6427965.20 Hz
    9618643.46 Hz
    12813982.60 Hz
    16011186.08 Hz
    2028860.26 Hz
    2547046.35 Hz
    3804709.93 Hz
    5083523.17 Hz
    7622348.78 Hz
    10161761.59 Hz
    2034879.93 Hz
    4068439.96 Hz

    Thonnyなどで main.py としてPicoに書き込めばそのまま動作します。PIO 0はGP22の可変出力、PIO 1はGP21の固定137kHz出力に使っているので、他の用途でPIOブロック0・1を使う場合は番号の調整が必要です。


    3.3V与えたツマミでファミコンノイズが「チクガガゴー」と雷として鳴らせると思いますよ。最高ですね。
    さて、クロックもステージも何とか設計できたとします。
    Raspberry Pi PICOには追加した部分がありますね。
    イニシャライズ部分です。 (他もあるけど)
    5V電源をONしたらクロックを進めながら一定時間5Vを出力して0Vになる回路。
    TR-909だとここの赤丸部分ですね。


    ソース中に137kHzクロックも出力するようにしていますが、これはTR-909専用です。
    あとは出力電圧が10Vppに出来ればEurorackモジュールのPseudo Noise Generatorの完成です。

    ちなみにTR-909のクロックの周波数は?

    R = 33kΩ(フィードバック抵抗)
    R = 10kΩ(直列抵抗)
    C = 100pF

    f=1/2.2xRxC

    ここでRは支配的な抵抗。この構成では R1とR2の直列合成 が時定数を決めます:

    R=33k+10k=43kΩ

    f=1/9.46x10の-6乗=106kHz

    なんだかネット上では値がおかしいような・・・まぁ実際は20%程度誤差があってもちゃんとノイズですから。
    137kHz部分を106kHzにしますか?どうしましょう。

    もっと安いNE555で考えてみる。

    確かに。その考えは正しい。ただしロータリースイッチを付けた時点で値段は高くなります。
    とりあえず考えてみましょう。

    NESのクロック表がこちら

    NES APU クロック分周テーブル

    ベースクロック:1.789773 MHz

    分周数周波数備考
    ÷4447.443 kHz
    ÷8223.722 kHz
    ÷16111.861 kHz← TR-909(106~137kHz)に近いのは13~16
    ÷3255.930 kHz
    ÷6427.965 kHz
    ÷9618.643 kHz
    ÷12813.983 kHz
    ÷16011.186 kHz
    ÷2028.860 kHz
    ÷2547.046 kHz
    ÷3804.710 kHz
    ÷5083.523 kHz
    ÷7622.349 kHz
    ÷10161.761 kHz
    ÷20340.880 kHz = 880 HzA4(ラ)×2
    ÷40680.440 kHz = 440 HzA4(ラ)

    ÷16 が発振回路の ~106 kHz に最も近い値です。というか誤差の範囲内なのでこだわる必要もありません。
    447kも223kも要らないと言えば要りません。ホワイトノイズですから。早過ぎてループに聞こえたらNGです。
    安く作るとしたら500kHzまで発信できるNE555の出番ですかね。

    NE555 定数算出(RA・RB共にE24系列最適化)

    f=1.44(RA+2RB)×Cf = \frac{1.44}{(R_A + 2R_B) \times C}

    RA+2RBの合計値をE24の組み合わせで目標に最も近づける方針で算出。


    定数表

    目標周波数CRA (E24)RB (E24)RA+2RB実現周波数誤差
    111.861 kHz100pF1.0kΩ6.2kΩ13.4kΩ107.5 kHz-3.9%
    55.930 kHz100pF2.0kΩ12kΩ26.0kΩ55.4 kHz-0.9%
    27.965 kHz100pF1.5kΩ25kΩ51.5kΩ27.96 kHz0.0%
    18.643 kHz100pF3.0kΩ36kΩ75.0kΩ19.2 kHz+3.0%
    13.983 kHz100pF2.0kΩ51kΩ104.0kΩ13.85 kHz-1.0%
    11.186 kHz100pF3.0kΩ62kΩ127.0kΩ11.34 kHz+1.4%
    8.860 kHz100pF1.6kΩ82kΩ165.6kΩ8.70 kHz-1.8%
    7.046 kHz1nF1.2kΩ100kΩ201.2kΩ7.16 kHz+1.6%
    4.710 kHz1nF2.0kΩ150kΩ302.0kΩ4.77 kHz+1.3%
    3.523 kHz1nF2.7kΩ200kΩ402.7kΩ3.57 kHz+1.5%
    2.349 kHz1nF3.3kΩ300kΩ603.3kΩ2.39 kHz+1.7%
    1.761 kHz1nF1.8kΩ400kΩ801.8kΩ1.796 kHz+2.0%
    0.880 kHz10nF1.6kΩ81kΩ163.6kΩ0.880 kHz0.0%
    0.440 kHz10nF1.6kΩ160kΩ321.6kΩ0.448 kHz+1.8%

    備考

    • 1.761 kHz18.643 kHz がやや誤差大きめ。トリマ併用を推奨
    • RB≫RAを維持しているためデューティ比は概ね 52〜55% 程度に収まります
    • 400kΩはE24に存在しないため、390kΩ+10kΩ直列で構成してください
    • ホワイトでない色のあるノイズ(8kHz以下)が3%以内であれば音程としては優秀です。気になるなら半固定抵抗でもつけて下さい。おそらくその価値は無いでしょう。
    555部分を切り替えるのはアナログスイッチで大丈夫でしょう。

    ぃゃ、これはDIYとしては楽しいですが安くならないぞと。
    やはりRaspberry Pi PICOのクロックの正確さが超絶便利です。
    ピン数も余っているので鍵盤着けてNESノイズシンセ出来ちゃいますもんね。
    ADCの変換の速さもいい感じですよね。


    コメント