SNSへはこちら

CH559マイコンをいじってみよう(3) - タイマー

マイコンの要であるタイマーについて設定していきましょう。

ただ、Intel 8051 Core に元々付いている Timer を説明しても面白くないので、今回はこのマイコン特有のペリフェラルである Timer3 をやっていきましょう。

Timer3の設定

こちらは本マイコンオリジナルのペリフェラルで、拡張機能とでもいいましょうかね。こちらは入力キャプチャモードやら色々と多機能です。今回は割り込みを使った設定をしていきます(あとの機能はあまり興味ないので)。

コード全景

説明の前に完成コードをお見せしましょう。意外とシンプルなので、これとデータシートを照らし合わせれば何をしているか分かる方もいるでしょう。

void timer3_irq_init(void) {
    T3_SETUP |= 1 << 0; // Allow access to divisor latch
    T3_CK_SE_L = 2400 & 0xFF;
    T3_CK_SE_H = 2400 >> 8;
    T3_SETUP &= ~(1 << 0);

    T3_END_H = (20000 - 1) >> 8;
    T3_END_L = (20000 - 1) & 0xff;

    EA = 1;
    IE_EX |= 1 << 1;
    T3_SETUP |= 1 << 7;

    T3_CTRL &= ~(1 << 1);
    T3_CTRL |= 1 << 2; // Count start
}

割り込み関数の記述は以下。

void timer3_isr(void) __interrupt(7) {
    P1 ^= 1 << 3;
    T3_STAT = 1 << 4;
}

初期化内容

データシートに書いてある説明通りにやればいい...とおもいきや、それだけではうまく動きません 。説明が不親切なので幸運にも掲載されているブロック図を参照する必要があります。

ペルリフェラルに入力されるクロック分周比

まずは基本ということで手順通りに行きます。カウンタに入力されるクロックの分周比を設定しましょう。
T3_SETUP レジスタの bT3_EN_CK_SE ビット(0bit 目)を1にすれば当該設定レジスタへのアクセスが可能になります。
入力されているクロックは Fsys そのものですので、16bit 範囲内で分周比を設定します。またこの T3_CK_SE_H下位4ビットしか有効ではないことに注意してください。つまり {T3_CK_SE_H:T3_CK_SE_L} の最大値は 0x0FFF となります。

そして最後に念の為アクセス許可を無効化しておきましょう。

// Fsys = 48MHz を 2400分周して20kHzに(本当は1kHzにしたかったがビット幅の制約上...)
    T3_SETUP |= 1 << 0; // Allow access to divisor latch
    T3_CK_SE_L = 2400 & 0xFF;
    T3_CK_SE_H = 2400 >> 8;
    T3_SETUP &= ~(1 << 0);

カウントEND値設定

詳しくは全く書かれていませんが、どうやらこのタイマーはアップカウントのようです(多分...データシートから察するに)。このタイマーはカウント上限値を設定できるのでその名もズバリなレジスタで設定しておきましょう。これでLチカ周期が0.5Hzになります。

    T3_END_H = (20000 - 1) >> 8;
    T3_END_L = (20000 - 1) & 0xff;

よくわからないけど、どうせハードウェア実装的な関係-1 しておく必要があるんだろ、としておきました。ちょっと試してみましたが、これで正しいようです。Verilog でハードウェア記述をやっていたおかげでペリフェラルの気持ちになれました。嬉しい。

でも動かない

一応説明どおりにやってはみましたが、動きません。つまりここハマリポイントです。
にっちもさっちも行かないので先述通りブロック図を見ます。

割り込みを起こすのに必要な経路を赤で示しておきました。そして、上でやった設定以外に必要な部分を緑色の丸で囲んであります。

bT3_CNT_EN はいいとして、bT3_IE_END, bT3_IE_ACT はデータシート読んでもそれっぽい説明無いですよ?あれ??

これがスクショですが、なんか Capture Mode と記述がありますねえ...どうやらこのマイコンは「内部でも外部でも関係ないから、クロックを利用するのをキャプチャと呼ぶ!」としているようです。そもそも Capture Mode の定義はどこにも記述がありません。そりゃビットの説明を読んでもピンとこないわけですわ。これで一件落着

main関数

呼び出し側はこんな感じになります。ただ、もしファイル分割をしている場合は、sdcc の仕様により main 関数のあるソースコードに少なくとも関数プロトタイプを記述せねばならないので注意。たとえば

void timer3_isr(void) __interrupt(7);

のように。それでは以下、main 関数側の記述。

int main(void) {
    clock_init();
    timer3_irq_init();


    PORT_CFG &= ~(1 << 1); // P1.x: Push-Pull
    PORT_CFG |= (1 << (1+4)); // P1.x: 10mA max
    P1_DIR |= 1 << 3;
    P1 = 0;

    while(1) {
    }
}

void timer3_isr(void) __interrupt(7) {
    P1 ^= 1 << 3;
    T3_STAT = 1 << 4;
}

おまけ:ms_wait

指定したミリ秒だけ待つ関数はやはり重要ですよね。
ということでこちらで作ってみました。使用ペリフェラルは Timer0 です。

void ms_wait(unsigned int msec) {
    TMOD |= (1 << 1); // Mode2: TL0 is a counter and TH0 is auto-reload value
    TH0 = (unsigned char)(256 - 40); // 8051 Timer Clock is already divided by 12
    TR0 = 1; // Count Start

    for(int i = 0; i < msec; i++) {
        int tick = 0;
AGAIN:
        while( !TF0 );
        TF0 = 0;
        tick++;
        if( tick < 100 ) goto AGAIN;
    }

    TR0 = 0; // Count Stop
}

さて今回は以上!!次回はタイマー関連ということで PWM です。