NESのノイズジェネレーターについて
| IC3個は驚くしかない。 |
疑似乱数の秘密はフィードバックにあり
イメージできます?
いざ、シミュレーションしてみましょう。
次はクロック部分をAIで生成させましょう。
NTSC 4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068 (2046 in old)
このコードは、SAMD21マイコンのGCLK (Generic Clock Controller) および TCC (Timer/Counter for Control Applications) という機能を直接利用して、正確な周波数を効率的に生成します。
回路接続
まず、以下のように部品を接続してください。
A0ピン: ボリューム(可変抵抗)の真ん中の端子に接続します。ボリュームの両端の端子は、それぞれ 5V と GND に接続します。
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ピン: 常に約kHzのクロックを出力
* - 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 = 300000UL; // 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ピン: 固定クロック(約300kHz)の初期設定
//================================================================
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 / 160 = 300,000 Hz (300kHz)
GCLK->GENDIV.reg = GCLK_GENDIV_DIV(160) | // 分周比を160に設定
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(汎用クロック生成器)を使って 160分周 します。
Hz となり、安定したクロックを生成します。
生成したクロックを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関数を呼び出して周波数を更新します。これにより、無駄な計算を省いています。
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(固定出力)
- 常時300kHz固定(起動後は一切変更しない別のPIOステートマシンで駆動)
- 初期化用に起動時20mSはGP20(26pin)の信号を初期値として入力するとノイズが生成される。
主な周波数一覧:
| 分周比 | 出力周波数 |
|---|---|
| 4 | 447443.13 Hz |
| 8 | 223721.56 Hz |
| 16 | 111860.78 Hz |
| 32 | 55930.39 Hz |
| 64 | 27965.20 Hz |
| 96 | 18643.46 Hz |
| 128 | 13982.60 Hz |
| 160 | 11186.08 Hz |
| 202 | 8860.26 Hz |
| 254 | 7046.35 Hz |
| 380 | 4709.93 Hz |
| 508 | 3523.17 Hz |
| 762 | 2348.78 Hz |
| 1016 | 1761.59 Hz |
| 2034 | 879.93 Hz |
| 4068 | 439.96 Hz |
Thonnyなどで main.py としてPicoに書き込めばそのまま動作します。PIO 0はGP22の可変出力、PIO 1はGP21の固定300kHz出力に使っているので、他の用途でPIOブロック0・1を使う場合は番号の調整が必要です。
NES APU クロック分周テーブル
ベースクロック:1.789773 MHz
| 分周数 | 周波数 | 備考 |
|---|---|---|
| ÷4 | 447.443 kHz | |
| ÷8 | 223.722 kHz | ← TR-909(300kHz)に近いのは6 |
| ÷16 | 111.861 kHz | |
| ÷32 | 55.930 kHz | |
| ÷64 | 27.965 kHz | |
| ÷96 | 18.643 kHz | |
| ÷128 | 13.983 kHz | |
| ÷160 | 11.186 kHz | |
| ÷202 | 8.860 kHz | |
| ÷254 | 7.046 kHz | |
| ÷380 | 4.710 kHz | |
| ÷508 | 3.523 kHz | |
| ÷762 | 2.349 kHz | |
| ÷1016 | 1.761 kHz | |
| ÷2034 | 0.880 kHz = 880 Hz | A4(ラ)×2 |
| ÷4068 | 0.440 kHz = 440 Hz | A4(ラ) |
447kも223kも要らないと言えば要りません。ホワイトノイズですから。早過ぎてループに聞こえたらNGです。
安く作るとしたら500kHzまで発信できるNE555の出番ですかね。
NE555 定数算出(RA・RB共にE24系列最適化)
RA+2RBの合計値をE24の組み合わせで目標に最も近づける方針で算出。
定数表
| 目標周波数 | C | RA (E24) | RB (E24) | RA+2RB | 実現周波数 | 誤差 |
|---|---|---|---|---|---|---|
| 111.861 kHz | 100pF | 1.0kΩ | 6.2kΩ | 13.4kΩ | 107.5 kHz | -3.9% |
| 55.930 kHz | 100pF | 2.0kΩ | 12kΩ | 26.0kΩ | 55.4 kHz | -0.9% |
| 27.965 kHz | 100pF | 1.5kΩ | 25kΩ | 51.5kΩ | 27.96 kHz | 0.0% |
| 18.643 kHz | 100pF | 3.0kΩ | 36kΩ | 75.0kΩ | 19.2 kHz | +3.0% |
| 13.983 kHz | 100pF | 2.0kΩ | 51kΩ | 104.0kΩ | 13.85 kHz | -1.0% |
| 11.186 kHz | 100pF | 3.0kΩ | 62kΩ | 127.0kΩ | 11.34 kHz | +1.4% |
| 8.860 kHz | 100pF | 1.6kΩ | 82kΩ | 165.6kΩ | 8.70 kHz | -1.8% |
| 7.046 kHz | 1nF | 1.2kΩ | 100kΩ | 201.2kΩ | 7.16 kHz | +1.6% |
| 4.710 kHz | 1nF | 2.0kΩ | 150kΩ | 302.0kΩ | 4.77 kHz | +1.3% |
| 3.523 kHz | 1nF | 2.7kΩ | 200kΩ | 402.7kΩ | 3.57 kHz | +1.5% |
| 2.349 kHz | 1nF | 3.3kΩ | 300kΩ | 603.3kΩ | 2.39 kHz | +1.7% |
| 1.761 kHz | 1nF | 1.8kΩ | 400kΩ | 801.8kΩ | 1.796 kHz | +2.0% |
| 0.880 kHz | 10nF | 1.6kΩ | 81kΩ | 163.6kΩ | 0.880 kHz | 0.0% |
| 0.440 kHz | 10nF | 1.6kΩ | 160kΩ | 321.6kΩ | 0.448 kHz | +1.8% |
備考
- 1.761 kHz と 18.643 kHz がやや誤差大きめ。トリマ併用を推奨
- RB≫RAを維持しているためデューティ比は概ね 52〜55% 程度に収まります
- 400kΩはE24に存在しないため、390kΩ+10kΩ直列で構成してください
- ホワイトでない色のあるノイズ(8kHz以下)が3%以内であれば音程としては優秀です。気になるなら半固定抵抗でもつけて下さい。おそらくその価値は無いでしょう。



コメント
コメントを投稿