NESのノイズジェネレーターについて
イメージできます?
bit(ステージ) | Ex-ORで抜き取るステージ |
---|---|
3 | 1, 3 |
4 | 3, 4 |
5 | 3, 5 |
6 | 5, 6 |
7 | 6, 7 |
8 | 4, 5, 6, 8 |
9 | 5, 9 |
10 | 7, 10 |
11 | 9, 11 |
12 | 4, 10, 11, 12 |
13 | 8, 11, 12, 13 |
14 | 2, 12, 13, 14 |
15 | 14, 15 |
16 | 4, 13, 15, 16 |
17 | 14, 17 |
いざ、シミュレーションしてみましょう。
次はクロック部分を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ピン: 常に約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
関数を呼び出して周波数を更新します。これにより、無駄な計算を省いています。
コメント
コメントを投稿