基本仕様
NTSC クロック周波数: 3.579545 MHzの半分の1.7897725 MHz。入手は3.5MHzのほうが入手しやすい。
目標分周比: 4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068
https://akizukidenshi.com/catalog/g/g101685/
https://akizukidenshi.com/catalog/g/g112492/
分周比の実現方法
- バイナリーカウンターを動かして特定条件をNAND回路で拾ってその信号をフリップフロップに与えて50%なクロックとして使う。
- 16あるリセット信号のどれをフリップフロップに与えるかで16種類のクロックを切り替える。この仕組みも必要。
バイナリーカウンターTC74HC4040APの仕様(CD4040B)
12ビットバイナリカウンタ (Q1-Q12)
分周範囲: 2~4096
出力: Q1(2^1), Q2(2^2), ..., Q12(2^12=4096)
リセット回路の仕様
96, 160, 202, 254分周以降にはNANDゲートを使ったリセット回路を追加:
所望のカウント値でリセット信号を生成。
例: 96分周 = 96(10進) = 1100000(2進) → Q6とQ5がHIGHの時にリセット
NAND回路のオンパレードだな・・・
8入力NANDなんて使ったことないな。最適化をAIにお願いしました。
// 共通パターンの定義
PATTERN_A = NAND(Q8, Q7, Q6, Q5, Q4, Q3, Q2) // 254用
PATTERN_B = NAND(Q9, Q8, Q7, Q6, Q5, Q4, Q3) // 508用
// 最適化リセット式
RESET_96 = NAND(Q7, Q6)
RESET_160 = NAND(Q8, Q6)
RESET_202 = NAND(PATTERN_A, Q4) // 254からQ4(16)を除外
RESET_254 = PATTERN_A
RESET_380 = NAND(Q9, PATTERN_A) // 254 + 256
RESET_508 = PATTERN_B
RESET_762 = NAND(Q10, PATTERN_A) // 254 + 512
RESET_1016 = NAND(Q10, Q9, PATTERN_A) // 254 + 512 + 256
RESET_2034 = NAND(Q11, Q10, Q9, Q8, Q7, Q6, Q5, Q2)
RESET_4068 = NAND(Q12, Q11, Q10, Q9, Q8, Q7, Q6, Q3)
PATTERN_A = NAND(Q8, Q7, Q6, Q5, Q4, Q3, Q2) // 254用
PATTERN_B = NAND(Q9, Q8, Q7, Q6, Q5, Q4, Q3) // 508用
// 最適化リセット式
RESET_96 = NAND(Q7, Q6)
RESET_160 = NAND(Q8, Q6)
RESET_202 = NAND(PATTERN_A, Q4) // 254からQ4(16)を除外
RESET_254 = PATTERN_A
RESET_380 = NAND(Q9, PATTERN_A) // 254 + 256
RESET_508 = PATTERN_B
RESET_762 = NAND(Q10, PATTERN_A) // 254 + 512
RESET_1016 = NAND(Q10, Q9, PATTERN_A) // 254 + 512 + 256
RESET_2034 = NAND(Q11, Q10, Q9, Q8, Q7, Q6, Q5, Q2)
RESET_4068 = NAND(Q12, Q11, Q10, Q9, Q8, Q7, Q6, Q3)
8入力NANDx4,2入力NANDx5,3入力NANDx1,
このまま作るとNANDだけでICが6つは必要ですね。私はこの構成で作り続けてもよいのですが想像以上に面積をとってしまうので考えを変更します。同じ面積で値段も変わらないのであればより多機能で柔軟なMCU化を選択します。80年代ならPAL/GALですね。
学習目的であれば上の表がほぼ答えなので作れるでしょう。
精密でなければNE555で抵抗をスイッチで変える方法もあります。
12KHz以上は精密でなくとも問題ありません。
水晶振動子
せっかくのクロック設計なので基本部分は紹介しましょう。
2本足の水晶振動子が基本です。
リンクはこちら
水晶振動子の周波数で33pFや6.8KΩの部分が変わります。1MΩはバイアス抵抗といいます。
水晶振動子は強烈なタンク回路なので上のインバーターが動くアナログ動作な環境で発信してくれます。
つまりCを変えても周波数が殆ど動くことがありません。
気を付けるとしたらインバーターはUB(アンバッファー)型番を使いましょう。型番にUかUBが付いています。
バッファードは内部でしきい値が急峻 → 線形性が悪く発振開始しにくいので適しません。UBならNAND回路でも構いません。
アナログの領域なので回路自体の配線は短くしないと発振しない可能性もあります。
あまったインバーターはこう処理します。
| ファミリ(例) | 未使用入力の処理 | 出力 |
|---|---|---|
| CMOS(74HC**, 74HCU**, 74AC** など) | High か Low に直接接続(または他の入力にリンク) | オープン |
| TTL / LS(74LS**, 74ALS** 等) | Low に落とすと電流が流れて無駄な消費 → Highにプルアップが安全 | オープン |
これは入力を明らかにしたままにすれば出力が暴れないという考え方です。
ちなみにインバーターの並列接続は電流が増えますので意図的にそうする事もあります。
オープン(何も接続しない)にすることで電流は増えません。
あと発振するという事はノイズ源でもあります。音関係の回路のそばには置きたくないです。
基本の2本足の次はTTL出力の4本足ですが多くを語らずとも電源をつなげば苦も無く出力が得られるので特に説明も要らないでしょう。
以下ハードウェアでのアプローチではなくソフトウェアでのアプローチで多くのLogicをまとめてしまう話題ですので興味がわかないかもしれません。
水晶振動子+CD4024B+NE555
CD4024BはQ1~Q7の分周器なので128までの分周の殆どまで(6出力分)は使えます。残るのは18khz以下ですのでNE555に96分周以下(10出力分)を作っていけば出来そうです。
マイクロコントローラーでのクロック生成
オルガンみたいに全ピンから各クロックを出力するか、
1ピンだけ出力でスイッチで切り替えるかです。
DIP8なPIC12F1822-I/Pで限界まで使ってみますね。
Timer0,2を切替えてクロック生成だけでなく
ノイズジェネレーターを組込んでツマミでZAXXONの出撃音(!!)が鳴ります。
ちなみに64分周より高い周波数はおそらく使いません。人間の耳で判別困難だからです。
/** PIC12F1822 マルチクロック/LFSR出力ファームウェア (仕様変更版)* ------------------------------------------------------------* Fosc = 32MHz (内蔵HFINTOSC 8MHz + 4xPLL)** RA5 (2pin) : LFSR1 最終ステージbit出力* RA4 (3pin) : リセット直後100ms間だけHigh、以降Low* RA3 (4pin) : デジタル入力。High=RA2を137kHz固定に強制。Low=AN0選択の分周比。* (この入力による切替はリセット動作を伴わない)* RA2 (5pin) : リセット直後100ms間は固定8分周のクロックを出力。* その後はAN0(16段階)で選んだ分周比、またはRA3=Highなら137kHz固定。* AN1 (RA1/6pin) : LFSR更新レート可変(440Hz〜14080Hz)* AN0 (RA0/7pin) : RA2の分周比を16段階選択** --- 実装方式 ---* RA2の高速な波形は CCP1 + Timer2 のハードウェアPWM(50%デューティ)で生成する。* これによりCPU/割り込みのオーバーヘッドなしに高周波数(最大約447kHz)まで* 安定して出力できる。ただしTimer2の周期レジスタ(PR2)は8bit(最大255)しか* ないため、下限は約1953Hz程度までしかカバーできない。* それより低い周波数(1016/2034/4068分周)は、負荷が軽く正確に出せる* Timer0の割り込みトグル方式にフォールバックする(モードを動的に切り替える)。* ------------------------------------------------------------*/#include <xc.h>#include <stdint.h>// ---------------- CONFIG BITS ----------------#pragma config FOSC = INTOSC#pragma config WDTE = OFF#pragma config PWRTE = ON#pragma config MCLRE = OFF // RA3をデジタル入力として使用するため#pragma config CP = OFF#pragma config CPD = OFF#pragma config BOREN = ON#pragma config CLKOUTEN = OFF#pragma config IESO = OFF#pragma config FCMEN = OFF#pragma config WRT = OFF#pragma config PLLEN = ON // 4xPLL常時有効(8MHz -> 32MHz)#pragma config STVREN = ON#pragma config BORV = LO#pragma config LVP = OFF#define _XTAL_FREQ 32000000UL// ---------------- 出力モード ----------------typedef enum { MODE_TIMER0 = 0, MODE_PWM = 1 } clkmode_t;typedef struct {clkmode_t mode;// Timer0モード用uint8_t t0_option; // OPTION_REG下位4bit(PSA,PS2,PS1,PS0)uint8_t t0_reload; // TMR0リロード値(256-N)// PWMモード用 (CCP1 + Timer2)uint8_t t2ckps; // T2CON<T2CKPS1:0> (00=1:1,01=1:4,10/11=1:16)uint8_t pr2; // PR2値uint16_t duty10; // 10bit デューティ値(概ね(pr2+1)相当で50%)} div_entry_t;// index0(=4068分周,約440Hz,最遅) 〜 index15(=4分周,約447kHz,最速)static const div_entry_t g_divTable[16] = {/* 0: 4068分周 (~439.9Hz) Timer0 */ {MODE_TIMER0, 0x05, 114, 0, 0, 0},/* 1: 2034分周 (~880.3Hz) Timer0 */ {MODE_TIMER0, 0x04, 114, 0, 0, 0},/* 2: 1016分周 (~1760.6Hz) Timer0 */ {MODE_TIMER0, 0x03, 114, 0, 0, 0},/* 3: 762分周 (~2347.4Hz) PWM */ {MODE_PWM, 0, 0, 0b10, 212, 213},/* 4: 508分周 (~3521.1Hz) PWM */ {MODE_PWM, 0, 0, 0b10, 141, 142},/* 5: 380分周 (~4717.0Hz) PWM */ {MODE_PWM, 0, 0, 0b10, 105, 106},/* 6: 254分周 (~7042.3Hz) PWM */ {MODE_PWM, 0, 0, 0b10, 70, 71},/* 7: 202分周 (~8849.6Hz) PWM */ {MODE_PWM, 0, 0, 0b01, 225, 226},/* 8: 160分周 (~11173.2Hz) PWM */ {MODE_PWM, 0, 0, 0b01, 178, 179},/* 9: 128分周 (~13986.0Hz) PWM */ {MODE_PWM, 0, 0, 0b01, 142, 143},/*10: 96分周 (~18691.6Hz) PWM */ {MODE_PWM, 0, 0, 0b01, 106, 107},/*11: 64分周 (~27777.8Hz) PWM */ {MODE_PWM, 0, 0, 0b01, 71, 72},/*12: 32分周 (~55944.1Hz) PWM */ {MODE_PWM, 0, 0, 0b00, 142, 143},/*13: 16分周 (~111111Hz) PWM */ {MODE_PWM, 0, 0, 0b00, 71, 72},/*14: 8分周 (~222222Hz) PWM */ {MODE_PWM, 0, 0, 0b00, 35, 36},/*15: 4分周 (~444444Hz) PWM */ {MODE_PWM, 0, 0, 0b00, 17, 18},};#define DIV8_INDEX 14u // 起動直後固定「8分周」に対応するテーブル番号// 137kHz固定(RA3=High時): prescale1, PR2=57 -> 約137931Hzstatic const div_entry_t g_entry137k = {MODE_PWM, 0, 0, 0b00, 57, 58};// 起動100ms判定用(divisor8のPWM周波数 ~222222Hz、1周期ごとにTMR2IF発生)#define STARTUP_TICKS 22222UL// ---------------- グローバル変数 ----------------static volatile uint8_t g_startupDone = 0;static volatile uint32_t g_startupTickCount = 0;static volatile uint32_t g_lfsr1 = 0x12345678UL;static volatile uint16_t g_tmr1Reload = 0;static volatile uint8_t g_tmr0Reload = 114;static int8_t g_lastAppliedIdx = -1; // -2:137kHz固定, -1:未設定, 0-15:通常テーブル// ---------------- ADC ----------------static uint16_t ADC_Read(uint8_t channel){ADCON0 = (uint8_t)((channel << 2) | 0x01);__delay_us(5);ADCON0bits.GO_nDONE = 1;while (ADCON0bits.GO_nDONE) {;}return (uint16_t)((((uint16_t)ADRESH) << 8) | ADRESL);}// ---------------- 初期化 ----------------static void OSC_Initialize(void){OSCCONbits.IRCF = 0b1110; // 8MHz -> PLLで32MHzOSCCONbits.SCS = 0b00;while (!OSCSTATbits.PLLR) {;}}static void IO_Initialize(void){ANSELA = 0x03; // AN0,AN1のみアナログTRISA = 0x0B; // RA0,RA1,RA3=入力 / RA2,RA4,RA5=出力LATA = 0x00;LATAbits.LATA4 = 1; // 起動直後100msだけHigh}static void ADC_Initialize(void){ADCON1 = 0xE0; // ADFM=1, ADCS=110(FRC), ADPREF=00(VDD)ADCON0bits.ADON = 1;}static void Timer1_Initialize(void) // LFSR (RA5) 用{T1CONbits.TMR1CS = 0b00;T1CONbits.T1CKPS = 0b00;g_tmr1Reload = 65536u - (8000000UL / 440UL);TMR1H = (uint8_t)(g_tmr1Reload >> 8);TMR1L = (uint8_t)(g_tmr1Reload & 0xFF);PIR1bits.TMR1IF = 0;PIE1bits.TMR1IE = 1;T1CONbits.TMR1ON = 1;}// RA2出力を指定テーブルエントリで駆動する(Timer0/PWM切替を含む)static void ApplyClockEntry(const div_entry_t *e){if (e->mode == MODE_TIMER0) {// PWM(CCP1)を止めてピンをLATA制御に戻すCCP1CON = 0x00;T2CONbits.TMR2ON = 0;TRISAbits.TRISA2 = 0;INTCONbits.T0IE = 0;OPTION_REG = (OPTION_REG & 0xF0) | e->t0_option;OPTION_REGbits.T0CS = 0;g_tmr0Reload = e->t0_reload;TMR0 = g_tmr0Reload;INTCONbits.T0IF = 0;INTCONbits.T0IE = 1;} else {// Timer0トグルを止めてCCP1 PWMに切替INTCONbits.T0IE = 0;T2CONbits.TMR2ON = 0;T2CONbits.T2CKPS = e->t2ckps;PR2 = e->pr2;uint16_t duty = e->duty10; // 10bit値。下位2bitをCCP1CON<5:4>へCCPR1L = (uint8_t)(duty >> 2);CCP1CONbits.DC1B = (uint8_t)(duty & 0x03);CCP1CON = (CCP1CON & 0xCF) | 0b00001100; // CCP1M=1100(PWMモード)TRISAbits.TRISA2 = 0;T2CONbits.TMR2ON = 1;}}static void Timer0_Initialize(void){// 起動直後は固定8分周(DIV8_INDEX = PWMモード)で開始ApplyClockEntry(&g_divTable[DIV8_INDEX]);g_lastAppliedIdx = DIV8_INDEX;}// ---------------- 割り込みサービスルーチン ----------------void __interrupt() ISR(void){// --- Timer0モード時: RA2トグル(低周波数域のみ) ---if (INTCONbits.T0IE && INTCONbits.T0IF) {TMR0 = g_tmr0Reload;LATAbits.LATA2 ^= 1;INTCONbits.T0IF = 0;}// --- Timer2 (PWM周期一致): 起動100ms計測用 ---if (PIE1bits.TMR2IE && PIR1bits.TMR2IF) {if (!g_startupDone) {g_startupTickCount++;if (g_startupTickCount >= STARTUP_TICKS) {g_startupDone = 1;LATAbits.LATA4 = 0;}}PIR1bits.TMR2IF = 0;}// --- Timer1: LFSR更新 (RA5) ---if (PIE1bits.TMR1IE && PIR1bits.TMR1IF) {TMR1H = (uint8_t)(g_tmr1Reload >> 8);TMR1L = (uint8_t)(g_tmr1Reload & 0xFF);g_lfsr1 = (g_lfsr1 >> 1) ^ (-(g_lfsr1 & 1u) & 0xD0000001u);LATAbits.LATA5 = (uint8_t)(g_lfsr1 & 1u);PIR1bits.TMR1IF = 0;}}// ---------------- メイン ----------------void main(void){OSC_Initialize();IO_Initialize();ADC_Initialize();Timer1_Initialize();PIE1bits.TMR2IE = 1; // 起動100ms計測に使うため、PWM開始前に有効化しておくTimer0_Initialize(); // 起動時8分周(PWM)を適用、Timer2始動INTCONbits.PEIE = 1;INTCONbits.GIE = 1;while (1) {if (g_startupDone) {if (PORTAbits.RA3 == 1) {// RA3=High: 137kHz固定に強制if (g_lastAppliedIdx != -2) {ApplyClockEntry(&g_entry137k);g_lastAppliedIdx = -2;}} else {uint16_t an0 = ADC_Read(0);uint8_t idx = (uint8_t)(an0 >> 6); // 上位4bit(0-15)if (g_lastAppliedIdx != (int8_t)idx) {ApplyClockEntry(&g_divTable[idx]);g_lastAppliedIdx = (int8_t)idx;}}}// AN1: 440Hz〜14080HzでLFSR更新レートを線形補間uint16_t an1 = ADC_Read(1);uint32_t freq = 440UL + ((13640UL * (uint32_t)an1) / 1023UL);uint32_t n = 8000000UL / freq;if (n < 2UL) {n = 2UL;}if (n > 65535UL) {n = 65535UL;}g_tmr1Reload = (uint16_t)(65536UL - n);}}
このようにマイクロコントローラーは多くのLogicICやA/Dそしてクロックや記憶装置を備えているので、ハードに詳しい人が適切に使うと全てにおいてパフォーマンスが高いです。別のマイクロコントローラーへの移植はAIにソースを読み込ませて相談してみましょう。良い結果が出るはずです。
最後のコードならLogicとPICで2つのLFSRノイズがとれるわけですから(RA3に5VいれるとTR-909用クロック)
面白いですよ。NESの独特なループを無くして64分周より高い周波数が不要なら贅沢なステレオLFSRも可能です。
こんな方法もあります。
NE555による固定周波数発信器
この方法だとR,Cをアナログスイッチで切り替えるだけですがスイッチングにコストがかかります。
でもモノシンセみたいにとってもアナログですね。好きな方は挑戦してみましょう。

コメント
コメントを投稿