ラズピコ SPI を使ってみる

  • 2023.01.27
  • SPI
ラズピコ SPI を使ってみる

皆さん こんにちは。

今回は SPI を使って気圧センサーモジュールとつないでみます。

気圧センサーモジュール

今回使った気圧センサーモジュールは こちら です。

端子設定で I2C と SPI のインターフェースを切り替えることができるのでマイコンペリフェラルの取り扱いを練習するのに良さそうです。

デバイスのデータシートは こちら です。

接続例

配線のしやすさを考慮して、こちらでは以下のように接続しました。

環境構築やプロジェクトのつくり方

環境構築やプロジェクトのつくり方、VSCodeの起動については、このブログの一番最初の記事に書きました。

環境構築してみたい方は こちら をご覧になってください。

プロジェクトの作成

以下の設定でプロジェクトを作成します。

Project Name: picoSpi
Library Options の SPI interface をチェックする
Console Options の Console over UART をチェックする
IDE Options の Create VSCode project をチェックする
Debugger: は PicoProbe を選択する

VSCodeを起動する

起動後、何かファイルが開いてたら File – Close Folder で閉じた後、File – Open Folder から picoSpi のフォルダーを辿って選択します。

こちらの環境では C:\Users\m3925\Documents\Pico\pico-project-generator\picoSpi です。

m3925の部分は皆さんのユーザー名に置き換えてください。

vscode関連の json ファイルは前回作成済のプロジェクトのものをドラッグ&ドロップでコピー&ペーストすると楽です。

ソースコードを書く

今回はファイルを分割してみました。
まずは picoSpi.c を以下の通りに編集します。

picoSpi.c

VSCodeのツリーから picoSpi.c を選択して次のように編集し、保存します。

#include <stdio.h>
#include "LPS25HB.h"
#include "hardware/spi.h"
#include "pico/stdlib.h"

// SPI Defines
// We are going to use SPI 0, and allocate it to the following GPIO pins
// Pins can be changed, see the GPIO function select table in the datasheet for
// information on GPIO assignments

int main() {
    uint8_t sbuf[20];
    uint8_t rbuf[20];

    stdio_init_all();

    // SPI initialisation. This example will use SPI at 1MHz.
    spi_init(spi0, 1000 * 1000);

    spi_set_format(spi0, 8, 1, 1, SPI_MSB_FIRST);

    gpio_set_function(PIN_MISO, GPIO_FUNC_SPI);
    gpio_set_function(PIN_CS, GPIO_FUNC_SIO);
    gpio_set_function(PIN_SCK, GPIO_FUNC_SPI);
    gpio_set_function(PIN_MOSI, GPIO_FUNC_SPI);

    // Chip select is active-low, so we'll initialise it to a driven-high state
    gpio_set_dir(PIN_CS, GPIO_OUT);
    gpio_put(PIN_CS, 1);

    puts("Hello, SPI!");

    bool result = LPS25HB_init(spi0, sbuf, rbuf);
    if (result) {
        puts("LPS25HB initialize success.");
        sbuf[0] = CTRL_REG1;
        sbuf[1] = WAKE_UP;
        gpio_put(PIN_CS, 0);
        sleep_us(2);
        LPS25HB_write(spi0, sbuf, 2);
        gpio_put(PIN_CS, 1);
        sleep_ms(10);
        sbuf[0] = P_ADRS | 0xc0;
        gpio_put(PIN_CS, 0);
        sleep_us(2);
        LPS25HB_write(spi0, sbuf, 1);
        LPS25HB_read(spi0, rbuf, 3);
        gpio_put(PIN_CS, 1);

        int pressure = rbuf[2] << 16 | rbuf[1] << 8 | rbuf[0];
        pressure /= 4096;
        printf("Today's atmospheric pressure is %d[hPa].", pressure);
    } else {
        puts("LPS23HB Init failure.");
    }
    return 0;
}

続いて LPS25HB.c を作成します。

LPS25HB.cの作成と編集

まず File - New File から入力ボックスに LPS25HB.c と入力し Enterキーを押します。
picoI2c のディレクトリーが出るので、そのまま Create File ボタンを押します。
続いて LPS25HB.c を以下の通りに編集します。

#include "LPS25HB.h"
#include <stdio.h>  // implicit declaration of function 'puts' への対策

bool LPS25HB_init(spi_inst_t *spi, uint8_t *sbuf, uint8_t *rbuf) {
    bool result;
    sbuf[0] = WHO_AM_I | 0x80;
    sbuf[1] = 0;
    gpio_put(PIN_CS, 0);
    sleep_us(2);
    result = LPS25HB_write(spi, sbuf, 1);
    if (result) {
        bool result2 = LPS25HB_read(spi, rbuf, 1);
        if (result2) {
            if (LPS25HB_DEVICE_CODE == rbuf[0]) {
                gpio_put(PIN_CS, 1);
                return true;
            } else {
                gpio_put(PIN_CS, 1);
                return false;
            }
        }
    } else {
        gpio_put(PIN_CS, 1);
        return false;
    }
}

bool LPS25HB_write(spi_inst_t *spi, uint8_t *buf, int length) {
    int ret = spi_write_blocking(spi, buf, length);
    if (ret == length) {
#ifdef PICO_DEBUG
        puts("SPI write success.");
#endif
        return true;
    } else {
#ifdef PICO_DEBUG
        if (PICO_ERROR_GENERIC == ret) {
            puts("PICO Error Generic.");
        } else if (PICO_ERROR_TIMEOUT == ret) {
            puts("PICO Error timeout.");
        }
#endif
    }
    return false;
}

bool LPS25HB_read(spi_inst_t *spi, uint8_t *buf, int length) {
    int ret = spi_read_blocking(spi, 0, buf, length);
    if (ret == length) {
#ifdef PICO_DEBUG
        puts("SPI read success.");
#endif
        return true;
    } else {
#ifdef PICO_DEBUG
        if (PICO_ERROR_GENERIC == ret) {
            puts("PICO Error Generic.");
        } else if (PICO_ERROR_TIMEOUT == ret) {
            puts("PICO Error timeout.");
        }
#endif
    }
    return false;
}

続いて LPS25HB.h を作成します。

LPS25HB.hの作成と編集

File - New File から入力ボックスに LPS25HB.h と入力し Enterキーを押します。
picoI2c のディレクトリーが出るので、そのまま Create File ボタンを押します。
続いて LPS25HB.h を以下の通りに編集します。

#ifndef __LPS25HB_H
#define __LPS25HB_H

#include "hardware/spi.h"
#include "pico/stdlib.h"

#define PICO_DEBUG

#define PIN_MISO 16  // 21pin
#define PIN_CS 17    // 22pin
#define PIN_SCK 18   // 24pin
#define PIN_MOSI 19  // 25pin

#define LPS25HB_ADRS 0x5c
#define WHO_AM_I 0x0f
#define CTRL_REG1 0x20
#define WAKE_UP 0x90
#define P_ADRS 0x28
#define LPS25HB_DEVICE_CODE 0xbd

bool LPS25HB_init(spi_inst_t *, uint8_t *, uint8_t *);
bool LPS25HB_write(spi_inst_t *i2c, uint8_t *, int);
bool LPS25HB_read(spi_inst_t *i2c, uint8_t *, int);

#endif /* __LPS25HB_H */

CMakeLists.txtの編集

ファイルを追加したので、CMakeLists.txt を編集する必要があります。
add_executable() に LPS25HB.c を追加します。

add_executable(picoSpi picoSpi.c LPS25HB.c)

これで追加したファイルもビルドしてもらえるようになります。

それから最適化しないオプションをつけるとデバッグしやすくなることがあります。
「最適化しないオプション」をつける場合には以下の2行を追加します。

add_compile_options(-std=c11 -O0 -g -Wall)
add_compile_options(-std=c++17 -O0 -g -Wall)

私は快適なデバッグを優先するので、こちらを使っています。

(良く理解せずに使っているので何か不備があったらすみません)

PC側の準備

SPI通信の動作確認用として Tera Term を使います。
API関数がうまく動作したか等の結果を通信ログ出力するようにしました。

Tera Termをお持ちでない方は こちら からダウンロードしてお使いください。

インストール後、 Tera Term を起動し、シリアルポートでPicoprobeのCOMポートを選択します。

もしCOMポートが複数ある場合には、Picoprobe以外の通信ケーブルをはずしてデバイスマネージャーでCOMポート番号を確認しておきます。

起動後、設定メニューのシリアルポートから以下の設定を行います。

スピート : 115200
データ : 8 bit
パリティ : なし
ストップビット : 1
フロー制御 : none

(設定値からスピードだけ変更すれば良いはずです)

今回使ったSPIのAPI

spi_init()

第1引数:SPIのインスタンスを指定します。
第2引数:ボーレート(クロックの速度)を指定します。

デバイスは 10MHz まで動作するようですが、ここでは 1MHz を指定しました。


spi_set_format()

第1引数:SPIのインスタンスを指定します。
第2引数:データ長を指定します。
第3引数:クロックの極性を指定します。
 デバイスに合わせて設定します。
第4引数:クロックの位相を指定します。
 デバイスに合わせて設定します。
第5引数:LSB_FIRST or MSB_FIRST のいずれかを指定します。
 デバイスに合わせて設定します。
 この気圧センサーモジュールは MSB_FIRST です。

クロックの極性と位相に関しては以下のサイトが参考になると思います。
気圧センサーモジュールのデータシート Figure 13 等と見比べてご確認ください。
SPIモードは3に該当します。

SPIの基本を学ぶ


spi_write_blocking()

SPIスレーブに対して指定バイト数分書き込みを行います。
第1引数:SPIのインスタンスを指定します。
第2引数:書き込み用のバッファを指定します。
第3引数:書き込むバイト数を指定します。


spi_read_blocking()

SPIスレーブから指定バイト数分読み込みを行います。
第1引数:SPIのインスタンスを指定します。
第2引数:出力用の値
 クロック出力時に繰り返し送信される値です。
 通常0を指定しますが、0以外を指定するデバイスもあるために指定できるようになっているようです。
第3引数:読み込み用のバッファを指定します。
第4引数:読み込むバイト数を指定します。

コードの説明

SPIに使う端子は配線しやすい位置のピン番号を選びました(21, 22, 24, 25番)

sleep_ms(), sleep_us() による待ち時間は適当な間を入れただけで特に数値に意味はありません。

デバイスに対して WHO_AM_I (0x0f) のコードを送り、1バイト読むとデバイスコードが返ってきます。
デバイスコードが LPS25HB_DEVICE_CODE (0xbd) であれば正しく応答できていると考えて良いでしょう。

LPS25HB_init() で上記の処理を行っています。

その後、Control Register1(CTRL_REG1) に WAKE_UP (0x90) のコードを送りモジュールを起こします。
そして、P_ADRS に連続するアドレスから3バイトの値を読みます。
P_ADRS に 0xc0 を OR すると読み込み動作でかつ、アドレスが自動でインクリメントされる機能を使っています。

読んできた3バイトを合成して 4096 で割ると気圧[hPa]を得ることができます。

詳しくはデータシートをご覧になってください。

動作させてみる

それでは F5キーを押してプログラムを動作させてみます。
Tera Term に計測した気圧が表示されれば成功です。
成功した時の画像を貼っておきます。

ついでに起動時に (WHO_AM_I | 0x80) を送りコード (0xbd) が返ってきた様子を観測した波形を貼っておきます。

(波形観測用にコードを若干編集した時の図です)

上から、
CS, CLK, MOSI, MISO の順です。

GitHub に picoSpi のコードをアップしましたのでご自由にお使いください。
(ただし当方では動作の保証は行っていません)

お疲れさまでした。

SPIカテゴリの最新記事