SNSへはこちら

CH559マイコンをいじってみよう(6) - SPIとエラッタ疑惑

2/11 追記:
なんか公開順を間違えましたw この記事を公開して今知ったので、取り下げはせずそのまま公開しておきます。以後通し番号が狂いますが許してください。

拡張ペリフェラルとして SPI が載っています。便利ですね。(できればもっと頑張って I2C も欲しかった)

んでこちらも若干ハマったり、これエラッタじゃね?と思われる挙動があったので、その報告も兼ねます。
多分エラッタじゃなくて僕の勘違いや間違いなんだろうなあ、と信じたいですがいかがですかね。有識者コメント頼みます。

SPI初期化コード全景/説明

早速載せちゃいます。これ結構設定項目が少なくてシンプルなのよ。
今回は 16bit 転送を前提として設定しました。というのは毎回恒例、MCP4922 (DAC) を動かしたいから。

でもこのペリフェラルは 8bit 転送専用なので、8bit x 2 で 16bit としている感じです。

void spi_init(void) {
    SPI0_CK_SE = 255; // divide Fsys

    SPI0_CTRL = 0xE0; // Make pin outputs enabled
    SPI0_CTRL |= 1 << 3; // Mode 3.
    // Now SCS pin is not used for the implementation of 16bit transmission.
    // So SCS is marked as "dummy".
    // The actual CS is assigned as follows:
    // CS:         P1.2(#45)
    // SCS(dummy): P1.4(#47)
    // MOSI:       P1.5(#48)
    // MISO:       P1.6(#1)
    // SCK:        P1.7(#2)
    PORT_CFG &= ~(1 << 1); // P1.x: Push-Pull
    PORT_CFG |= (1 << (1+4)); // P1.x: 10mA max
    P1_DIR |= (1 << 2) | (1 << 4) | (1 << 5) | (1 << 7);
    P1 |= (1 << 2); // Deassert CS
}

説明

ではコード・ペリフェラルの説明を。これまでのペリフェラルと似たようなところもあるのでかいつまむ形になります。

ではまず以下から。こちらは SPI0 のピン出力を有効化するものです。どちらかというと IO ポート側に働きかけている感じだと思います。もちろんポート側で別途入出力設定が必須ですが。今回は MISO, MOSI, SCK の出力を有効化しています。というかこのレジスタで設定できる全てです。

また、このレジスタの 4bit 目は bS0_DATA_DIR という名前で、0 なら SPI データは出力、1 なら入力となります。今回は使いませんが、データを受信したいときはこのビットを1にして、データレジスタに適当な値を書き込むと SCK のクロックが出力されます。だいたい他のマイコンと使い方同じですね〜

SPI0_CTRL = 0xE0; // Make pin outputs enabled

以下の設定によって、SPI0 を Mode 3 で動かしています。このペリフェラルは Mode 0 と Mode 3 で選べるのですが、今回は Mode 3。
Mode 0 ではちょっとおかしなところがあるので、それは後で述べます。

それと、今回は記述していませんが SPI0_SETUP の 7bit 目を1にするとスレーブモードになります。今回はマスタ動作なので触らず。
なお、SPI1 はマスターモードの動作のみなのでご注意を。

後はポート設定をしておしまい!設定は簡単です。

ちなみにですが、コメント中にあるピン説明で SCS(dummy) となっています。こちらの説明を。SPI のセクションでは全く説明がないのですが、Pin Descriptions でちょこっとだけ

SCS is the chip select input,

だけ書かれています。おそらくスレーブモードのときの Chip Select 信号の入力なんでしょう。とんでもねえ罠だ。なので代わりに別ピンの IO ポートを使いました。

データ送信関数

配列で受けて、それを順々に送ります。指定データを送り終わったらチップセレクトを High にして通信シーケンスを終えます。

void spi_send(unsigned char dat[], int len) {
    P1 &= ~(1 << 2); // Assert CS
    for(int i = 0; i < len; i++) {
        SPI0_DATA = dat[i];
        while( !(SPI0_STAT & (1 << 3)) );
    }
    P1 |= (1 << 2); // Deassert CS
}

実際にデータを送ってみる

初期化コード中に記述してあるピン通りに MCP4922 に接続して以下のように書けば動くはず!LED ボヤァが実現するはずです。

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

    unsigned short dat = 0;
    unsigned char arr[2] = {0, 0};
    while(1) {
        arr[0] = (0x7 << 4) | ((dat >> 8) & 0xF);
        arr[1] = dat & 0xFF;
        spi_send(arr, 2);
        dat = (dat + 1) % (1 << 12);
        ms_wait(10);
    }

不可解な動作

いつも僕は SPI では Mode 0 に設定する派ですが、今回に限って Mode 3 と設定しました。これには訳があります。

上の例で dat = 1000 と入力して実際に送信してみました。

Mode 0での送信波形

こんな感じです。上の黄色い波形は SCK で、下の青い波形は MOSI です。その下にはオシロの機能でデコードしたものになります。

それで、これでは動きません。でもちょっと考えてみてください。
送信するデータフォーマットは、0xFFF 以下のデータを 0x7 << 12 と OR した 16bit です。順を追ってみましょう。

まず 1000 は 16 進数で表記すると 0x03 E8 です。わかりやすいように先頭桁を0埋めして2桁で区切りました。
0x7 << 12 も同様に 0x70 00 となります。
これらを OR すると 0x73 E8 となるはずですよね。これはおかしい。

なんか設定間違っているんじゃないかと色々と試行錯誤しましたけど、それでもうまくいきません。エラッタなんでしょうか。

Mode 3での送信波形

ということで Mode 3 でやるといい感じになりました。