SNSへはこちら

EPSONマイコンC17で遊んでみよう(4) - タイマー

各ペリフェラルに関して細かくやっていく気は無いのですが、なんとなく解説したいものをピックアップして行きます。

T16 - 16ビットカウントダウンタイマー

現代のイケイケマイコンのような機能は全く無いです。単純にアンダーフローイベントのみです。シンプルなので設定しやすいですね。このマイコンの特徴としても、ある程度汎用性を出しつつ消費電力を抑えたマイコンですので、そこまで機能はなくてもいいんですねきっと。
TR というレジスタがリロードレジスタです。アンダーフローのたびにこの値がカウンタに読み込まれます。

割り込みを利用する際は ITC (Interrupt Controller) の設定が必要です。コンパイラ標準のスタートアップルーチンを使用した場合は、デフォルトで CPU レベルの割り込みが有効化されていますが、ITC サイドでの割り込み優先度レベルは 0 (無効)です。0 以外の値に設定すれば割り込み動作をし始めます。

ちょっと T16 の割り込みを利用したコードを書いてみました。これで 1.95 秒ごとに P04 につながった LED がチカチカするはずです。

#include <c17_regs.h>

void clock_init(void) {
    CLGOSC |= 1 << 2; // OSC3EN for system clock, 4MHz by default.
    while( !(CLGINTF & (1 << 2)) );
    CLGINTF = 1 << 2;
    CLGOSC |= 1 << 1; // OSC1EN for T16 Ch. 0, 32,768Hz.
    while( !(CLGINTF & (1 << 1)) );
    CLGINTF = 1 << 1;

    MSCPROT = 0x96;
    CLGSCLK |= 2 << 0;

    MSCPROT = 0;
}

void timer_init(void) {
    // clock source : OSC1 / 64 == 512Hz
    T16_0CLK = (1 << 0) | (0x6 << 4);
    T16_0CTL = 1; // T160 enabled
    T16_0TR = 1000 - 1; // Interrupt per 1.95 sec
    T16_0CTL |= 1 << 1; // Preset TR to its counter

    T16_0INTE = 1; // underflow interrupt
    ITCLV2 = 1 << 8;

    T16_0CTL |= 1 << 8; // counter start
}

volatile unsigned int g_t16Ch0Ticked;

int main(void) {
    clock_init();
    timer_init();
    P0IOEN |= 1 << 4;
    while(1) {
        while( !g_t16Ch0Ticked );
        g_t16Ch0Ticked = 0;
        P0DAT ^= 1 << (4 + 8);
    }
}

__attribute__((interrupt_handler))
void _vector09_handler(void) { // T16Ch0
    if( T16_0INTF ) {
        T16_0INTF = 1;
        g_t16Ch0Ticked = 1;
    }
}

ちなみに以下で、例の ms_wait を作りました。割り込みを使わずに。

#include <c17_regs.h>

void clock_init(void) {
    CLGOSC |= 1 << 2; // OSC3EN, 4MHz by default.
    while( !(CLGINTF & (1 << 2)) );

    MSCPROT = 0x96;
    CLGSCLK |= 2 << 0;

    MSCPROT = 0;
}

void timer_init(void) {
    // clock source : IOSC (700kHz)
    T16_0CTL = 1; // T160 enabled
    T16_0TR = 700 - 1; // Interrupt per 1 msec
    T16_0CTL |= 1 << 1; // Preset TR to its counter

    T16_0CTL |= 1 << 8; // counter start
}

void ms_wait(unsigned int ms) {
    for(int i = 0; i < ms; i++) {
        while( !(T16_0INTF & 1) );
        T16_0INTF = 1;
    }
}

int main(void) {
    clock_init();
    timer_init();
    P0IOEN |= 1 << 4;
    while(1) {
        ms_wait(200);
        P0DAT ^= 1 << (4 + 8);
    }
}

T16B - 16bit PWMタイマー

名前こそ "PWM" ですが、それだけではなくコンペアマッチ機能を持つタイマーと考えたほうが良さそうです。STM32 の基本タイマーからエンコーダーモードを抜いた感じと捉えてください。
で、このタイマーはコンペアマッチ時に指定ポートの出力レベルを変化させることが出来るようです。今回はLチカの手段として、LED ボヤァのために使いました。電子ホタルとも呼ぶらしい。

#include <c17_regs.h>
#define P0FNCSEL   REG(0x420e)
#define P0UPMUX2   REG(0x4304)
#define T16B0CLK   REG(0x5000)
#define T16B0CTL   REG(0x5002)
#define T16B0MC        REG(0x5004)
#define T16B0INTF   REG(0x500a)
#define T16B0INTE   REG(0x500c)
#define T16B0CCCTL0    REG(0x5010)
#define T16B0CCR0  REG(0x5012)

void clock_init(void) {
    CLGOSC |= 1 << 2; // OSC3EN, 4MHz by default.
    CLGOSC |= 1 << 1; // OSC1EN, 32,768Hz ext.
    while( !(CLGINTF & (1 << 2)) );
    while( !(CLGINTF & (1 << 1)) );

    MSCPROT = 0x96;
    CLGSCLK |= 2 << 0;

    MSCPROT = 0;
}

#define UPMUX_PERISEL_T16B 4
#define UPMUX_PERICH_CH0 0
#define UPMUX_PPFNC_TOUTx0 1
void t16b_init(void) {
    P0MODSEL |= 1 << 4; // Assigned to peripheral
    P0FNCSEL |= 1 << (4 * 2); // UPMUX

    // UPMUX is set as: P04 == T16B Ch.0 TOUT00
    P0UPMUX2 = (UPMUX_PERISEL_T16B << 0) | (UPMUX_PERICH_CH0 << 3) | (UPMUX_PPFNC_TOUTx0 << 5);

    T16B0CLK = (0 << 0) | (5 << 4); // Clock = IOSC / 32 = 21,875Hz
    T16B0CTL |= 1; // Peripheral enabled
    T16B0CCCTL0 = (0x2 << 2) | (1 << 12); // Inactive on MATCH, active on MAX ( up-count )
    T16B0MC = 100; // Max Count Data
    T16B0CCR0 = 0; // Compare Count Data

    T16B0INTE = 1; // interrupt on zero
    ITCLV5 |= 1 << 0;

    T16B0CTL |= 0x6; // Counter Preset & Count Start
}

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

    while(1) {
        asm volatile ("halt");
        asm volatile ("nop");
    }
}


__attribute__((interrupt_handler))
void _vector14_handler(void) {
    if( T16B0INTF & 1 ) {
        T16B0INTF = 1;
        T16B0CCR0 = (T16B0CCR0 < 100) ? T16B0CCR0 + 1 : 0;
    }
}

UPMUX という謎ペリフェラルをいじっているのですが、これはこの下で紹介します。端的に言うと、ポート機能のスイッチマトリクスですね。

...ところで、サンプルから取ってきたヘッダには、サンプルで使うレジスタしか定義がありません。ファイル名的に全部定義されているものかと思いましたがそうではない。メーカー自身が配布しているレジスタマクロファイルでこのパターンは初めてですよ。
それはそれとして、このファイルの定義に間違いが見つかりました。謎にハマったから勘弁してくれ。修正箇所は P0MODSEL で、以下のように修正します。

//#define P0MODSEL  REG(0x421c)
#define P0MODSEL   REG(0x420c)

...こう間違いがあったり不足がある状況なので、次からはレジスタマクロを自分の手で作ろうと思います。このファイルは一旦なしにします。

UPMUX - Universal Port Multiplexer

上の T16B でしれっと使ったこちら、結構便利だと思います。以下はテクニカルマニュアルから取ってきたものそのままですが、通信関係 + T16B のポートをある程度自由に割当できるのは便利だと思います。

割当できるポートは P0x, P1x, P2x, P3x であり全てではないですが結構柔軟に設定できますね。
ではどうやって割当をするのかということですが、

  1. 指定したいピンについて、PPORT の PxMODSEL を設定してピン機能をペリフェラルに割当
  2. PxFNCSELUPMUX に割当
  3. 対応する PxUPMUXy で、対応するペリフェラルの種類対応するチャネル番号を指定する

と言った感じです。UPMUX はまさにマトリクスであり、ピンとペリフェラルの仲介役なのですね。超便利。確か Atmel SAM とか LPC8xx シリーズにも似たようなのあった気がしますが、こういうの良いよね。

一旦タイマー (+ UPMUX) の話は以上とします。お次はかなり特徴的な RFC (Resistance/Frequency Converter ?) を取り上げていこうと思います。