PSoC 42xx は SPI コンポーネントが提供されていて、クロックは 4MHz まで設定できます。しかしながら、これを使って可能な限り速く MCP2515 と通信することはそれほど単純ではありません。
問題は、MCP2515 の命令は複数のバイトでできていることです。一つの命令は CS 信号で束ねられていて、命令を送っている最中はずっと L (イネーブル)にしておかないといけません。しかしながら、PSoC の SPI は CS 信号を直接制御できません。コンポーネントとの通信は FIFO を通じて行われており、FIFO がデータを受け取ると、内部で自動的に CS をイネーブルに変え、データの送信が終わって FIFO が空になると自動的に CS をディスエイブルに戻します。自動生成される API 関数はあまり効率が良くなくて、SPI のクロック周波数が高いとスピードに追い付けず、送信の間が空いてしまいます。そのためクロック周波数が高いと API 関数を使って MCP2515 の命令フレームを作ることができません。
この問題を解決するために、API 関数を使わずに SPI を制御する関数を書きました。方針は
- より低レベルのインタフェースを使って SPI とやり取りを行う
- いったん送信が始まったら可能な限り速く Tx FIFO にデータを送り込み、送信が途絶えないようにする
- データを送るのと並行して可能な限り早く Rx FIFO からデータを取り出す
いくつか制限事項があります
- SPI 設定の Rx および Tx バッファサイズは 4 でないといけない。これより大きいと、PSoC Creator がソフトウェアバッファを生成してしまいソースコードの管理が難しくなる
- 取り出したデータの最初の2バイトはダミーで意味をなさない。実際のデータは3バイト目から始まる。
- まだ実装していないがたぶんこの関数を実行中は割り込みを停止しておかないといけない
以下がソースコードです。SPI コンポーネント名は SPIM_CAN で種類は SPI マスタ (SCB を使わない) です。
#define CAN_CTL_READ 0x03 void mcp2515_read(uint8_t address, uint8_t data[], uint8_t length) { /* initialization */ uint8_t to_write = length; length += 2; /* flush rx buffer */ while (SPIM_CAN_GetRxBufferSize()) SPIM_CAN_ReadRxData(); /* wait until Tx FIFO becomes empty */ while (0u == (SPIM_CAN_TX_STATUS_REG & SPIM_CAN_STS_TX_FIFO_EMPTY)) {} CY_SET_REG8(SPIM_CAN_TXDATA_PTR, CAN_CTL_READ); // push instruction CY_SET_REG8(SPIM_CAN_TXDATA_PTR, address); // push address // loop until all bytes are retrieved while (length > 0) { // transmit 0 to receive a byte if (to_write > 0 && (SPIM_CAN_TX_STATUS_REG & SPIM_CAN_STS_TX_FIFO_NOT_FULL)) { CY_SET_REG8(SPIM_CAN_TXDATA_PTR, 0); --to_write; } // retrieve a byte if there is any in Rx FIFO if (SPIM_CAN_RX_STATUS_REG & SPIM_CAN_STS_RX_FIFO_NOT_EMPTY) { *data++ = CY_GET_REG8(SPIM_CAN_RXDATA_PTR); --length; } } }
MCP2515 から 16 バイト取り出してみました。4MHz のクロックで想定通りに動いています。