JR-100のビデオ出力のカラーNTSC化(3) 同期ずれが解消できない

レトロPC

本業でバタバタしてブランキング期間がありつつ、こちらも悪戦苦闘してきましたが、どうやら現在のやり方ではうまくいかないことが分かってきました。このまま突き進んでもあまり有益な情報は得られそうもありません。いったん現状をまとめて次の方式の検討に入ろうと思います。

回路について

まず回路はこうなりました。

コンパレータの選択とI/Oポートへの接続

当初はコンパレータとしてNJM360Dを使う計画でしたが、これだと回路全体で3電源が必要(+5V、-5V、PIC用の3.3V)が必要になってしまいます。これではやりたいことに対してちょっと回路が大げさになり過ぎるため、単電源のTL712CP(応答時間:25ns)を使うことにしました。
ここでTL712CPもLM1881Nもいずれも5V出力のため、PICのI/Oポートのうち5Vを受け付けるピンに接続しています(RB5~RB7)。

DA変換

ビデオ信号の出力部は、I/Oポートを5ビット分とそれに抵抗を接続して5bit DA変換とします。抵抗値は次の条件をもとに決定します。なお便宜上IREは同期信号の底を0IREとみなして考えます。

  • 出力を接続していないときに140IREでVp-pが2.0Vになるようにする(75Ωの負荷をつなぐとVp-p=1Vとなる)。
  •  このとき最大電圧は黄色を出力する際の最大振幅である173IRE(2.471V)である。
  • したがって1bitあたり2.471/32=0.072Vとなるように抵抗値を求める。
  • インピーダンス整合のため75Ωの抵抗を出力ピンに並列に入れる。

これにより各ビットの抵抗値は以下のようになります。実際の抵抗値はE24系列のものからなるべく近い値のものを選んでいます。

bit 計算上の
抵抗値(Ω)
電圧(V) 実際の
抵抗値(Ω)
実際の
電圧(V)
1 3130 0.0772 3000 0.0805
2 1528 0.1544 1500 0.1571
4 727 0.3089 680 0.3278
8 326 0.6178 330 0.6111
16 125 1.2355 120 1.2692

ビデオ出力

dsPIC33FJ32GP202のI/Oポートの出力電流は最大1mAのため、バッファ74HC541を間に入れることにしました。

実装

以上を踏まえたブレッドボード上で実装しました。

プログラムについて

基本的な構造は前々回の記事で示した通りです。PICの入力クロック3.5795454MHzをPLLで12倍にして、命令サイクルを42.95454MHzとしています。

ドットクロック

JR-100のドットクロックは7.15909MHzです。つまり1ドットあたり6命令サイクルの周期でサンプリングとドット出力をする必要があります。けっこうギリギリでしたがコードを工夫して何とか入りました。当然アセンブラで書く必要があり、さらにペナルティ無しでループを回すことができるdo命令が必須でした。

            "           do #306, 1f             \n\t"
            "           mov PORTB, w4           \n\t"
            "           btst.z w4, #5           \n\t"
            "           bsw.z [w1++], w3        \n\t"
            "           mov [w0++], w4          \n\t"
            "           mov.b [w2+w4], w4       \n\t"
            "1:         mov w4, PORTB           \n\t"

カラーバースト

カラーバーストは3.5795454MHzですから、カラーバースト1周期あたり12命令サイクル分の信号を出力可能です。実際には1周期あたり6個(位相が0度、60度、120度、180度、240度、300度)のデータを出せるようにしました。

// カラーバースト信号を出力する。
#define COLOR_BURST \
    __asm__( \
        "       mov #0, w0        \n\t" \
        "       mov #_color_burst_table, w1     \n\t" \
        "       add w1, w0, w0      \n\t" \
        "       do #54, 0f          \n\t" \
        "       mov.b [w0++], w1    \n\t" \
        "0:     mov w1, PORTB       \n\t" \
        "       mov.b #7, w0        \n\t" \
        "       mov w0, PORTB       \n\t" \
        "       repeat #53          \n\t" \
        "       nop                 \n\t" \
    : : : "w0", "w1");

ここでもdo命令が活躍しています。カラーバーストの各位相の値をテーブルに格納し、これの読み出しと出力をそれぞれ1命令で行うことにより12サイクルで6個のデータを出力することができています。

ところでカラーバーストの位相は、ビデオ信号として出力されていない間も含めてずっと位相が連続している必要があります。水平同期信号を生成してから既定の時間の後に必ず位相0度からカラーバーストを出力するため、水平同期の生成を少し待ち合わせる処理を入れました。具体的にはタイマー1を使って12サイクルマイに割り込みを発生するようにして、割り込みがかかった時点を水平同期信号出力のタイミングとしています。

水平同期

水平同期はJR-100の水平同期信号とはリンクせず、垂直同期開始時点から「PIC側」クロックの2688サイクル毎にタイマー2割り込みを発生させることで生成しています。JR-100の水平同期とリンクすると、JR-100側クロックとPIC側クロックが正確には一致していないことが要因で水平方向のブレが発生してしまうためであり(ビデオキャプチャ内のPLLが誤作動するため?)、これを回避するためにPIC側クロックに合わせてつねに一定の周期で水平同期信号を出力したかったためです。

プログラム

その他、gccのインラインアセンブラで使えるテクニックを多用して作ったプログラムを以下に示します。テクニックの解説はまたいつか。。。

// DSPIC33FJ32GP202 Configuration Bit Settings

// 'C' source line config statements

// FBS
#pragma config BWRP = WRPROTECT_OFF     // Boot Segment Write Protect (Boot Segment may be written)
#pragma config BSS = NO_FLASH           // Boot Segment Program Flash Code Protection (No Boot program Flash segment)

// FGS
#pragma config GWRP = OFF               // General Code Segment Write Protect (User program memory is not write-protected)
#pragma config GSS = OFF                // General Segment Code Protection (User program memory is not code-protected)

// FOSCSEL
#pragma config FNOSC = PRIPLL           // Oscillator Mode (Primary Oscillator (XT, HS, EC) w/ PLL)
#pragma config IESO = ON                // Internal External Switch Over Mode (Start-up device with FRC, then automatically switch to user-selected oscillator source when ready)

// FOSC
#pragma config POSCMD = XT              // Primary Oscillator Source (XT Oscillator Mode)
#pragma config OSCIOFNC = OFF           // OSC2 Pin Function (OSC2 pin has clock out function)
#pragma config IOL1WAY = ON             // Peripheral Pin Select Configuration (Allow Only One Re-configuration)
#pragma config FCKSM = CSDCMD           // Clock Switching and Monitor (Both Clock Switching and Fail-Safe Clock Monitor are disabled)

// FWDT
#pragma config WDTPOST = PS32768        // Watchdog Timer Postscaler (1:32,768)
#pragma config WDTPRE = PR128           // WDT Prescaler (1:128)
#pragma config WINDIS = OFF             // Watchdog Timer Window (Watchdog Timer in Non-Window mode)
#pragma config FWDTEN = OFF             // Watchdog Timer Enable (Watchdog timer enabled/disabled by user software)

// FPOR
#pragma config FPWRT = PWR128           // POR Timer Value (128ms)
#pragma config ALTI2C = OFF             // Alternate I2C  pins (I2C mapped to SDA1/SCL1 pins)

// FICD
#pragma config ICS = PGD2               // Comm Channel Select (Communicate on PGC1/EMUC1 and PGD1/EMUD1)
#pragma config JTAGEN = OFF             // JTAG Port Enable (JTAG is Disabled)

// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.

#include <xc.h>

#define IRE_0           (0)
#define IRE_40          (7)
#define IRE_100         (25)

#define SAMPLES_PER_SUBCARRIER    (12)

// n+1サイクルwaitする。
#define WAIT_CYCLES(n) \
    __asm__( \
        "       repeat #" #n "      \n\t" \
        "       nop                 \n\t" \
    );

// n+1サイクルの同期パルス(立下りパルス)を出力する。
#define SYNC_PULSE(n) \
    __asm__( \
        "       clr PORTB           \n\t" \
    ); \
    WAIT_CYCLES(n) \
    __asm__( \
        "       mov.b #7, w0        \n\t" \
        "       mov w0, PORTB       \n\t" \
    : : : "w0");

// 水平同期パルスを出力する。
#define HSYNC_PULSE SYNC_PULSE(197)

// 垂直同期パルスを出力する。
#define VSYNC_PULSE SYNC_PULSE(2481)

// カラーバースト信号を出力する。
#define COLOR_BURST \
    __asm__( \
        "       mov #0, w0        \n\t" \
        "       mov #_color_burst_table, w1     \n\t" \
        "       add w1, w0, w0      \n\t" \
        "       do #54, 0f          \n\t" \
        "       mov.b [w0++], w1    \n\t" \
        "0:     mov w1, PORTB       \n\t" \
        "       mov.b #7, w0        \n\t" \
        "       mov w0, PORTB       \n\t" \
        "       repeat #53          \n\t" \
        "       nop                 \n\t" \
    : : : "w0", "w1");

static unsigned char mode = 0;
static unsigned char line_number = 0;

__asm__("   .section    .ndata,data,near            \n\t"
        "   .type       _dot_value,@object          \n\t"
        "   .size       _dot_value,2                \n\t"
        "_dot_value:                                \n\t"
        "   .byte 7                                 \n\t"
        "   .byte 25                                \n\t"
        );

__asm__("   .section    .ndata,data,near            \n\t"
        "   .align      2                           \n\t"
        "   .type       _line_cache,@object         \n\t"
        "   .size       _line_cache,640             \n\t"
        "_line_cache:                               \n\t"
        "   .space 640,0                            \n\t"
        );

__asm__("   .section    .ndata,data,near            \n\t"
        "   .type       _color_burst_table,@object  \n\t"
        "   .size       _color_burst_table,67       \n\t"
        "_color_burst_table:                        \n\t"
        "   .rept 11                                \n\t"
        "   .byte 7, 10, 10, 7, 4, 4                \n\t"
        "   .endr                                   \n\t"
        "   .byte 7                                 \n\t"
);

__asm__("   .section   .ndata,data,near            \n\t"
        "   .align      2                          \n\t"
        "   .type      _branch_table,@object       \n\t"
        "   .size      _branch_table,512           \n\t"
        "_branch_table:                            \n\t"
        "   .short LINE_MODE_A0, LINE_MODE_A1, LINE_MODE_A2, LINE_MODE_A3, LINE_MODE_A4, LINE_MODE_A5, LINE_MODE_A6, LINE_MODE_A7   \n\t"   // 0 - 7
        "   .short LINE_MODE_A8, LINE_MODE_B, LINE_MODE_B, LINE_MODE_B, LINE_MODE_B, LINE_MODE_B, LINE_MODE_B, LINE_MODE_B   \n\t"   // 8 - 15
        "   .short LINE_MODE_B, LINE_MODE_B, LINE_MODE_B, LINE_MODE_B, LINE_MODE_B, LINE_MODE_B, LINE_MODE_B, LINE_MODE_B   \n\t"   // 16 - 23
        "   .short LINE_MODE_B, LINE_MODE_B, LINE_MODE_B, LINE_MODE_B, LINE_MODE_B, LINE_MODE_B, LINE_MODE_B, LINE_MODE_B   \n\t"   // 24 - 31
        "   .short LINE_MODE_B, LINE_MODE_B, LINE_MODE_B, LINE_MODE_B, LINE_MODE_B, LINE_MODE_B, LINE_MODE_B, LINE_MODE_B   \n\t"   // 32 - 39
        "   .short LINE_MODE_C1, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C   \n\t"   // 40 - 47
        "   .short LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C   \n\t"   // 48 - 55
        "   .short LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C   \n\t"   // 56 - 63
        "   .short LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C   \n\t"   // 64 - 71
        "   .short LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C   \n\t"   // 72 - 79
        "   .short LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C   \n\t"   // 80 - 87
        "   .short LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C   \n\t"   // 88 - 95
        "   .short LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C   \n\t"   // 96 - 103
        "   .short LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C   \n\t"   // 104 - 111
        "   .short LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C   \n\t"   // 112 - 119
        "   .short LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C   \n\t"   // 120 - 128
        "   .short LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C   \n\t"   // 128 - 135
        "   .short LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C   \n\t"   // 136 - 143
        "   .short LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C   \n\t"   // 144 - 151
        "   .short LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C   \n\t"   // 152 - 159
        "   .short LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C   \n\t"   // 160 - 167
        "   .short LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C   \n\t"   // 168 - 175
        "   .short LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C   \n\t"   // 176 - 183
        "   .short LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C   \n\t"   // 184 - 191
        "   .short LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C   \n\t"   // 192 - 199
        "   .short LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C   \n\t"   // 200 - 207
        "   .short LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C   \n\t"   // 208 - 215
        "   .short LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C   \n\t"   // 216 - 223
        "   .short LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C   \n\t"   // 224 - 231
        "   .short LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_C, LINE_MODE_D, LINE_MODE_D, LINE_MODE_D, LINE_MODE_D   \n\t"   // 232 - 239
        "   .short LINE_MODE_D, LINE_MODE_D, LINE_MODE_D, LINE_MODE_D, LINE_MODE_D, LINE_MODE_D, LINE_MODE_D, LINE_MODE_D   \n\t"   // 240 - 247
        "   .short LINE_MODE_D, LINE_MODE_D, LINE_MODE_D, LINE_MODE_D, LINE_MODE_D, LINE_MODE_E, LINE_MODE_E, LINE_MODE_E   \n\t"   // 248 - 255
);

// Timer2
void __attribute__((interrupt, no_auto_psv, shadow)) _T2Interrupt(void) {
    IFS0bits.T2IF = 0;
    // 各行の処理
    __asm__("       mov #_branch_table, w1      \n\t"
            "       mov #2, w0                  \n\t"
            "       mul.b _line_number          \n\t"
            "       mov [w1+w2], w0             \n\t"
            "       goto w0                     \n\t"
            : : : "w0", "w1", "w2");
    
    // line 0
    __asm__("LINE_MODE_A0:");
    VSYNC_PULSE
    // WAIT_CYCLES(200)
    __asm__("       bra EPILOGUE");

    // line 1
    __asm__("LINE_MODE_A1:");
    VSYNC_PULSE
    // WAIT_CYCLES(200)
    __asm__("       bra EPILOGUE");

    // line 2
    __asm__("LINE_MODE_A2:");
    VSYNC_PULSE
    // WAIT_CYCLES(200)
    __asm__("       bra EPILOGUE");

    // line 3
    __asm__("LINE_MODE_A3:");
    HSYNC_PULSE
    // WAIT_CYCLES(2484)
    __asm__("       bra EPILOGUE");

    // line 4
    __asm__("LINE_MODE_A4:");
    HSYNC_PULSE
    // WAIT_CYCLES(2484)
    __asm__("       bra EPILOGUE");

    // line 5
    __asm__("LINE_MODE_A5:");
    HSYNC_PULSE
    // WAIT_CYCLES(2484)
    __asm__("       bra EPILOGUE");

    // line 6
    __asm__("LINE_MODE_A6:");
    HSYNC_PULSE
    WAIT_CYCLES(17)
    COLOR_BURST
    // WAIT_CYCLES(2293)
    __asm__("       bra EPILOGUE");

    // line 7
    __asm__("LINE_MODE_A7:");
    HSYNC_PULSE
    WAIT_CYCLES(17)
    COLOR_BURST
    // WAIT_CYCLES(2293)
    __asm__("       bra EPILOGUE");

    // line 8
    __asm__("LINE_MODE_A8:");
    HSYNC_PULSE
    WAIT_CYCLES(17)
    COLOR_BURST

    // JR-100の垂直同期直前に短いパルスが入るためここで割り込みをリセットする。
    IFS1bits.INT1IF = 0;
    // line_number = 8;
    __asm__("       bra EPILOGUE");
    
    // 9行目~39行目
    __asm__("LINE_MODE_B:");
    HSYNC_PULSE
    WAIT_CYCLES(17)
    COLOR_BURST
    // goto文で置き換えられるか?
    __asm__("       bra EPILOGUE");

    // 40行目~235行目
    __asm__("LINE_MODE_C:");
    HSYNC_PULSE
    WAIT_CYCLES(17)
    COLOR_BURST
    WAIT_CYCLES(51)    // 56から前処理部分の命令数分(10サイクル)を減算する。
    
    // 映像信号のキャプチャと出力
    // w0(wreg): 表示するデータへのポインタ
    // w1: スキャンしたデータの格納領域へのポインタ
    // それぞれの領域は1データあたり16bitとする。
    __asm__("           mov #_line_cache, w1    \n\t"
            "           mov w1, w0              \n\t"
            "           mov #_dot_value, w2     \n\t"
            "           clr w3                  \n\t"

            "           mov.b #25, w4           \n\t"
            "           mov w4, PORTB           \n\t"
            "           do #306, 1f             \n\t"
            "           mov PORTB, w4           \n\t"
            "           btst.z w4, #5           \n\t"
            "           bsw.z [w1++], w3        \n\t"
            "           mov [w0++], w4          \n\t"
            "           mov.b [w2+w4], w4       \n\t"
            "1:         mov w4, PORTB           \n\t"
            : : : "w0", "w1", "w2", "w3", "w4");
    __asm__("       bra EPILOGUE");

    __asm__("LINE_MODE_C1:");
    HSYNC_PULSE
    WAIT_CYCLES(17)
    COLOR_BURST
    WAIT_CYCLES(56)    // 56から前処理部分の命令数分(10サイクル)を減算する。
    
    // 映像信号のキャプチャと出力
    __asm__("           mov.b #25, w0           \n\t"
            "           mov w0, PORTB           \n\t"
            "           do #306, 1f             \n\t"
            "           mov w0, PORTB           \n\t"
            "           nop                     \n\t"
            "           nop                     \n\t"
            "           nop                     \n\t"
            "           nop                     \n\t"
            "1:         nop                     \n\t"
            "           mov.b #7, w0            \n\t"
            "           mov w0, PORTB           \n\t"
            "       bra EPILOGUE"
            : : : "w0");
    
    // 236行目~252行目
    __asm__("LINE_MODE_D:");
    HSYNC_PULSE
    WAIT_CYCLES(17)
    COLOR_BURST
    __asm__("       bra EPILOGUE");

    // 253行目~255行目
    __asm__("LINE_MODE_E:");
    HSYNC_PULSE
    // __asm__("       bra EPILOGUE");

    __asm__("EPILOGUE:");
    line_number++;
    if (line_number == 0) {
        IEC0bits.T2IE = 0;
        IFS1bits.INT1IF = 0;
        IEC1bits.INT1IE = 1;
    }
}

// Timer1
// vsyncが発生しバースト信号をちょうどよく発行できるタイミングでこの割り込みが発生する。
void __attribute__((interrupt, no_auto_psv, shadow)) _T1Interrupt(void) {
    IEC1bits.INT1IE = 0;
    IEC0bits.T1IE = 0;

    IFS0bits.T2IF = 0;
    PR2 = 2687;
    TMR2 = 2685;
    IEC0bits.T2IE = 1;
}

// INT1: csync
void __attribute__((interrupt, no_auto_psv, shadow)) _INT1Interrupt(void) {
    IFS1bits.INT1IF = 0;

    __asm__("       cp0.b _mode                 \n\t");
    __asm__("       bra z, MODE0                \n\t");

    __asm__("MODE1:");
    __asm__("       inc.b _line_number          \n\t"
            "       cp0.b _line_number          \n\t"
            "       bra nz, EPILOGUE2           \n\t"
            : : : "w0");
    __asm__("       dec.b _mode                 \n\t"
            "       bra EPILOGUE2               \n\t"
            : : : );

    __asm__("MODE0:");
    IFS0bits.T1IF = 0;
    IEC0bits.T1IE = 1;

    // Timer1割り込みが発生するまで最大12サイクルあるため12個のnopで待ち合わせる。
    __asm__("       nop                         \n\t");
    __asm__("       nop                         \n\t");
    __asm__("       nop                         \n\t");
    __asm__("       nop                         \n\t");
    __asm__("       nop                         \n\t");
    __asm__("       nop                         \n\t");
    __asm__("       nop                         \n\t");
    __asm__("       nop                         \n\t");
    __asm__("       nop                         \n\t");
    __asm__("       nop                         \n\t");
    __asm__("       nop                         \n\t");
    __asm__("       nop                         \n\t");

    __asm__("EPILOGUE2:");
}

// INT2: vsync
void __attribute__((interrupt, no_auto_psv)) _INT2Interrupt(void) {
    IFS1bits.INT1IF = 0;
    IFS1bits.INT2IF = 0;
    IEC1bits.INT1IE = 1;
    IEC1bits.INT2IE = 0;
    line_number = 8;
    mode = 1;
}

int main(void) {
    // PLLの設定: Fosc = 14.31818MHz * 6 = 85.90908MHz
    CLKDIVbits.PLLPRE = 0; // N1 = 2
    PLLFBDbits.PLLDIV = 94; // M = 96
    CLKDIVbits.PLLPOST = 0; // N2 = 2
    
    while (OSCCONbits.LOCK != 1) {
        ;
    }

    // I/Oポートの設定
    AD1PCFGL = 0xffff;  // すべてのピンをディジタル入出力にする。
    TRISB = 0xffe0;      // RB0~RB4を出力、その他を入力とする。 

    // 外部割込みの設定
    RPINR0bits.INT1R = 7;   // CSYNC -> INT1
    RPINR1bits.INT2R = 6;   // VSYNC -> INT2
    
    // INT1R, INT2Rを設定するとIFS1で割り込みフラグが立ってしまうので
    // 明示的にクリアする。
    IFS1bits.INT1IF = 0;
    IFS1bits.INT2IF = 0;
    
    INTCON2bits.INT1EP = 1; // negative edge detect
    INTCON2bits.INT2EP = 1; // negative edge detect

    IEC1bits.INT2IE = 1;
    
    // タイマー
    T1CONbits.TCKPS = 0;    // 1:1 pre-scaler
    PR1 = SAMPLES_PER_SUBCARRIER - 1;
    T1CONbits.TON = 1;
    IFS0bits.T1IF = 0;
    IEC0bits.T1IE = 0;
    
    T2CONbits.TCKPS = 0;    // 1:1 pre-scaler
    PR2 = 2687;
    T2CONbits.TON = 1;
    IFS0bits.T2IF = 0;
    IEC0bits.T2IE = 0;
    
    T3CONbits.TCKPS = 0;
    T3CONbits.TON = 1;
    IFS0bits.T3IF = 0;
    IEC0bits.T3IE = 0;
    
    // 割り込み優先度の設定
    IPC0bits.T1IP = 7;
    IPC1bits.T2IP = 7;
    IPC5bits.INT1IP = 1;
    IPC7bits.INT2IP = 1;
    
    mode = 2;
    line_number = 0;
    
    PORTB = IRE_40;
    
    while (1) {
        ;
    }
    
    return 1;
}

結果

結果はこんなになりました。

左端と上端の白い線は、画面の枠を表示するためわざと生成しています。これを見てわかる通り、プログラム内で生成した左端と上端の線は安定して出力されている一方、キャプチャしたデータは左右方向に1ドット程度ブレが発生してしまっています。

また、実際の波形は以下です。

カラーバーストは想定通り位相0度から9周期分生成されています。カラーバーストの後ろの上方向のパルスは、画面左端の白線部分です。

水平方向のブレについては、これもおそらくJR-100とPICのクロックが一致していないためだと思われます。これを解消すべくバッファキャッシュを使ってサンプリングとデータ出力のタイミング差を計算式を求めて少しずらす処理を入れてみたのですがどうしても解決できず。今回の回路設計についてはここで断念しました。

次の方針ですが、以前の記事のコメントでくろょさんに教えていただいたSビデオ方式での実装を試してみようと思います。水平同期と映像出力はJR-100の出力そのままとすることで水平方向のブレはなくなることが期待できます。あとはカラーバーストのタイミングをどうするかですが……。

ということで次にチャレンジ!

コメント

タイトルとURLをコピーしました