セクションの複数ページをまとめています。 印刷またはPDF形式で保存...

もとのページに戻る

2024-10-18 現在

SPI

SPIバス (コントローラ側) の読み書き
SPIバス (コントローラ側) の読み書きを行います。

注意事項

定数

定数意味
const uint8_t
SPI_CONF::MSBFIRST
MSB を先頭ビットにする
const uint8_t
SPI_CONF::LSBFIRST
LSB を先頭ビットにする
const uint8_t
SPI_CONF::SPI_MODE0
SPI MODE 0 に設定する
const uint8_t
SPI_CONF::SPI_MODE1
SPI MODE 1 に設定する
const uint8_t
SPI_CONF::SPI_MODE2
SPI MODE 2 に設定する
const uint8_t
SPI_CONF::SPI_MODE3
SPI MODE 3 に設定する

初期化と終了

SPIバスの利用手続きはbegin()メソッドによります。

begin()

void begin(uint8_t slave_select, SPISettings settings)
SPISettings(uint32_t clock, uint8_t bitOrder, uint8_t dataMode)

ハードウェアの初期化を行います。

パラメータ解説
slave_select対象のペリフェラルのセレクトピンを指定する。0 : DIO19``1 : DIO0 (DIO 19 は予約されます)``2 : DIO1 (DIO 0,19 は予約されます)
settingsSPIのバス設定を指定します。

clock[hz]でSPIバスの周波数を指定します。指定した周波数に近いディバイザが選択されます。16Mhzまたは16Mhzを偶数で割った値になります。
bitOrderSPI_CONF::MSBFIRSTSPI_CONF::LSBFIRSTを指定します。
dataModeSPI_CONF::SPIMODE0..3を指定します。

void setup() {
  ...
  SPI.begin(0, SPISettings(2000000, SPI_CONF::MSBFIRST, SPI_CONF::SPI_MODE3));
  ...
}

void wakeip() {
  ...
  SPI.begin(0, SPISettings(2000000, SPI_CONF::MSBFIRST, SPI_CONF::SPI_MODE3));
  ...
}

end()

void end()

SPIのハードウェアの利用を終了します。

読み書き

読み書きの手続きは、以下の2種類あります。いずれかを選択して利用します。

使用例

次のサンプルコードは、Analog Devices の温度センサ ADT7310 から1秒おきに温度を取得し、シリアルポートへ出力します。

#include <TWELITE>
#include <SM_SIMPLE>

enum class STATE : uint8_t {
    INTERACTIVE = 255,
    INIT = 0,
    INIT_WAIT,
    SENSOR,
    LOOP_WAIT
};
SM_SIMPLE<STATE> step;

struct SensorData {
    uint8_t highByte;
    uint8_t lowByte;
    uint16_t rawValue;
    int32_t tempValue16th;
    div_result_i32 temperature;
} sensorData;

void setup() {
    step.setup(); // 状態マシンの初期化
}

void loop() {
    do {
        switch (step.state()) {
        case STATE::INIT: // 初期状態
            SPI.begin(0 /* DIO19をチップセレクトとして使用 */
                      , { 400000UL /* クロック周波数 */
                    , SPI_CONF::MSBFIRST
                    , SPI_CONF::SPI_MODE3
            }
                      );

            // ソフトウェアリセット
            SPI.beginTransaction();
            for (int i = 0; i < 4; i++) {
                SPI.transfer(0xFF);
            }
            SPI.endTransaction();

            // Continuous Readモード開始
            SPI.beginTransaction();
            SPI.transfer(0x54);
            SPI.endTransaction();

            step.set_timeout(300); // 待機時間の設定
            step.next(STATE::INIT_WAIT);
            break;

        case STATE::INIT_WAIT: // 待機
            if (step.is_timeout()) {
                step.next(STATE::SENSOR);
            }
            break;

        case STATE::SENSOR: // センサーデータの読み取り
            SPI.beginTransaction();
            sensorData.highByte = SPI.transfer(0x00);  // ダミーデータを送信してクロック信号を生成
            sensorData.lowByte = SPI.transfer(0x00);   // ダミーデータを送信してクロック信号を生成
            SPI.endTransaction();

            sensorData.rawValue = (((uint16_t)sensorData.highByte << 8) | sensorData.lowByte) >> 3;
            if (sensorData.rawValue & 0x1000) {
                sensorData.tempValue16th = int32_t(sensorData.rawValue) - 0x2000;
            } else {
                sensorData.tempValue16th = sensorData.rawValue;
            }

            // div100()を使用して温度計算
            sensorData.temperature = div100((sensorData.tempValue16th * 100) / 16);

            // 結果をシリアル出力
            Serial << crlf << sensorData.temperature.format() << "°C";

            step.set_timeout(1000); // 次のキャプチャまで待機
            step.next(STATE::LOOP_WAIT);
            break;

        case STATE::LOOP_WAIT: // 待機
            if (step.is_timeout()) {
                step.next(STATE::SENSOR);
            }
            break;

        default:
            break;
        }
    } while (step.b_more_loop());
}

ここでは、メンバ関数版のインタフェースを利用しています。

1 - SPI (メンバ関数版)

SPI (メンバ関数を使用する方法)
begin()メソッドによりハードウェアの初期化を行った後、beginTransaction()によりバスの読み書きができるようになります。beginTransaction()を実行するとSPIのセレクトピンが選択されます。読み書きはtransfer()関数を用います。SPIは読み出しと書き込みを同時に実行します。

beginTransaction()

void beginTransaction()
void beginTransaction(SPISettings settings)

バスの利用開始を行います。SPIのセレクトピンをセットします。

settingsパラメータを与えて呼び出した場合は、バスの設定を行います。

endTransaction()

void endTransaction()

バスの利用を終了します。SPIのセレクトピンを解除します。

transfer(), transfer16(), transfer32()

inline uint8_t transfer(uint8_t data)
inline uint16_t transfer16(uint16_t data)
inline uint32_t transfer32(uint32_t data)

バスの読み書きを行います。trasnfer()は8bit、transfer16()は16bit、transfer32()は32bitの転送を行います。

2 - SPI (ヘルパークラス版)

SPI (ヘルパークラスを使用する方法)
ヘルパークラス版はより抽象度の高い実装です。読み書きを行うオブジェクト transceiver を生成することでバスの利用を開始し、オブジェクトを破棄することでバスの利用を終了します。

if文の判定式内でオブジェクトの生成を行うことで、オブジェクトの有効期間はif節内のスコープに限定され、if節を抜けた時点でオブジェクトは破棄され、その時点でバスの利用終了の手続きを行います。

uint8_t c;
if (auto&& trs = SPI.get_rwer()) { // オブジェクトの生成とデバイスの通信判定
   // このスコープ(波かっこ)内が trs の有効期間。
   trs << 0x00; // 0x00 を mwx::stream インタフェースで書き出し
   trs >> c;    // 読み出したデータをcに格納。
}
// if 節を抜けるところで wrt は破棄され、バスの利用終了

また、読み書きオブジェクトは、mwx::streamインタフェースを実装しているため<<演算子などを利用できます。

  • バスの利用開始と終了をオブジェクトの有効期間と一致させることで、ソースコードの見通しを良くし、また終了手続きの記述漏れなどを防ぎます
  • mwx::streamインタフェースによる読み書き手続きを統一します

読み書き

読み込み処理とその終了手続きをスコープ内 if() { ... } で行うためのヘルパークラスを用いた読み込み方法。

inline uint8_t _spi_single_op(uint8_t cmd, uint8_t arg) {
    uint8_t d0, d1;
    if (auto&& x = SPI.get_rwer()) {
        d0 = x.transfer(cmd); (void)d0;
        d1 = x.transfer(arg);
        // (x << (cmd)) >> d0;
        // (x << (arg)) >> d1;
    }

    return d1;
}

上記では get_rwer() メソッドにより生成された x オブジェクトを用いて1バイトずつ読み書きを行っています。

  1. if(...) 内で x オブジェクトを生成します。同時にSPIバスのセレクトピンをセットします。(型は、型推論によるユニバーサル参照 auto&& で解決しています。)
  2. 生成した x オブジェクトには operator bool () が定義されており、判定式の評価として利用される。SPIバスの場合は常に true となる。
  3. x オブジェクトには uint8_t transfer(uint8_t) メソッドが定義されていて、これを呼び出すことでSPIに対して8bitの読み書き転送を行。
  4. if() { ... } スコープの末尾で x のデストラクタが呼び出され、SPIバスのセレクトピンを解除します。

get_rwer()

periph_spi::transceiver get_rwer()

SPIバスの読み書きに用いるワーカーオブジェクトを取得します。

ワーカーオブジェクト

transfer(), transfer16(), transfer32()

uint8_t transfer(uint8_t val)
uint16_t transfer16(uint16_t val)
uint32_t transfer32(uint32_t val)

それぞれ8bit,16bit,32bitの転送を行い、読み取り結果を書き込んだデータ幅と同じデータ幅で返す。

<<演算子

operator << (int c)
operator << (uint8_t c)
operator << (uint16_t c)
operator << (uint32_t c)

int型,uint8_t型は8bitの転送を行います。

uint16_t型、uint32_t型は、それぞれ16bitの転送、32bitの転送を行います。

転送結果は最大16バイトの内部FIFOキューに格納され >> 演算子により読み出します。バッファが大きくないので、転送都度読み出すことを想定します。

>>演算子

operator >> (uint8_t& c)
operator >> (uint16_t& c)
operator >> (uint32_t& c)

null_stream(size_t i = 1)
operator >> (null_stream&& p)

直前の転送と同じデータ幅の変数を指定します。

読み出した結果が不要の場合はnull_stream()オブジェクトを使用します。iで指定したデータバイト分だけ読み飛ばします。