TOPに戻る

Raspberry Pi Picoでプログラミング ⑩ i2c APIと気圧センサLPS25

 i2cは、クロックSCLとデータSDA信号のタイミングを利用したバスです。Picoではi2c0とi2c1の二組のバスがサポートされ、いろいろなピンで利用できるので、汎用性が高いです。

 次に示すのは、それぞれのバスで、利用できるピンの組み合わせです。

i2c0

[GP1,GP0]、[GP5,GP4]、[GP9,GP8]、[GP13,GP12]、[GP17,GP16]、[GP21,GP20]

i2c1

[GP3,GP2]、 [GP7,GP6]、 [GP11,GP10]、 [GP15,GP14]、 [GP19,GP18]、 [GP27,GP26]

 データ転送速度は100kHzがデフォルトで、400kHz、1MHzまで使えます。

●APIのファンクション

  • i2c_inst_t *i2c i2c0もしくはi2c1
  • baudrate Hzで指定
  • uint8_t addr 7ビット(もしくは10ビット)のアドレス
  • const uint8_t *src 送信するデータへのポインタ
  • uint8_t *dst データを受信するためのバッファへのポインタ
  • size_t len 長さ(バイト)

ファンクション 要約
uint i2c_init (i2c_inst_t *i2c, uint baudrate)

ハードウェアの初期化。ほかの関数を使う前に実行。デフォルトはマスタ。データ転送速度を指定でき、返り値には実際に設定された値が戻る

void i2c_deinit (i2c_inst_t *i2c) I2Cハードウェア・ブロックを無効にする。I2Cが使用されなくなった場合は、再度無効にする。再使用する前に再初期化する必要がある
uint i2c_set_baudrate (i2c_inst_t *i2c, uint baudrate) データ転送速度を指定し、返り値には実際に設定された値が戻る
void i2c_set_slave_mode (i2c_inst_t *i2c, bool slave, uint8_t addr) slave=trueでスレーブ・モードにセット。失敗するとマスタ・モードになる。addrはスレーブ・モードに使われるアドレス
static uint i2c_hw_index (i2c_inst_t *i2c) i2cのバス番号0、1が返る
int i2c_write_blocking_until (i2c_inst_t *i2c, uint8_t addr, const uint8_t *src, size_t len, bool nostop, absolute_time_t until) 指定されたバイト数をアドレスに書き込もうとし、指定された絶対時間に達するまでブロックする
int i2c_read_blocking_until (i2c_inst_t *i2c, uint8_t addr, uint8_t *dst, size_t len, bool nostop, absolute_time_t until) アドレスから指定されたバイト数を読み取もうとし、指定された絶対時間に達するまでブロックする
static int i2c_write_timeout_us (i2c_inst_t *i2c, uint8_t addr, const uint8_t *src, size_t len, bool nostop, uint timeout_us) トランザクション全体が完了するのを待つ時間を指定して、指定されたバイト数をアドレスに書き込
static int i2c_read_timeout_us (i2c_inst_t *i2c, uint8_t addr, uint8_t *dst, size_t len, bool nostop, uint timeout_us)

トランザクション全体の完了を待つ時間を指定して、アドレスから指定されたバイト数を読み取る

int i2c_write_blocking (i2c_inst_t *i2c, uint8_t addr, const uint8_t *src, size_t len, bool nostop)

指定されたバイト数をアドレスに書き込む。
最後のパラメタがtrueの場合、マスタは転送の終了時にバスの制御を保持し(Stopは発行されない)、次の転送はスタートではなくリピーテッド・スタートで開始される

int i2c_read_blocking (i2c_inst_t *i2c, uint8_t addr, uint8_t *dst, size_t len, bool nostop) アドレスから指定されたバイト数を読み取る。
最後のパラメタがtrueの場合、マスタは転送の終了時にバスの制御を保持し(Stopは発行されない)、次の転送はスタートではなくリピーテッド・スタートで開始される
static size_t i2c_get_write_available (i2c_inst_t *i2c) returnが0以外の場合、少なくともその数のバイトをブロックせずに書き込める
static size_t i2c_get_read_available (i2c_inst_t *i2c) 使用可能なデータがない場合は0、戻り値が0以外の場合は、少なくともその数のバイトをブロックせずに読み取れる
static void i2c_write_raw_blocking (i2c_inst_t *i2c, const uint8_t *src, size_t len) TXFIFOに直接書き込む
static void i2c_read_raw_blocking (i2c_inst_t *i2c, uint8_t *dst, size_t len) RXFIFOから直接読み取る

AdafruitのLPS25HBボードのおもなスペック

 実際にi2cで気圧センサLPS25のデータを読み出します。

  • 動作電圧 3.3V(デバイスは1.7~3.6 V)
  • 測定範囲 260~1260hPa
  • 確度 ±0.1hPa(25℃)、±1hPa(0~80℃)
  • インターフェース SPI、I2C。I2C用プルアップ抵抗は実装、5V用レベル変換回路実装
  • スレーブ・アドレス 0x5d

接続

 [GP9,GP8]のi2c0バスにつなぎます。このボードはSTEMMA QTと呼ばれるI2C専用の4ピンコネクタがついています。2本の信号線以外に、3.3VとGNDをつなぎます。

WHO_AM_I

 読み書きが正しくできるかどうかを、WHO_AM_I(0x0f)を読み出し、0xbdが返ってくることで確認します。

 pico/worksフォルダにlps25hbフォルダを作ります。なかに、CMakeLists.txtとlps25.cを入れます。

 lps25.cの内容です。


#include <stdio.h>
//#include string.h>
#include "pico/stdlib.h"
#include "hardware/i2c.h"
//#include "pico/binary_info.h"

static int lp25hb_addr = 0x5d;

#define I2C_PORT  i2c0
#define WHO_AM_I  0x0f

#define SDA_PIN 8  // GP8
#define SCL_PIN 9  // GP9

static void read_registers(uint8_t reg, uint8_t *buf, uint16_t len) {
    i2c_write_blocking(I2C_PORT, lp25hb_addr, &reg, 1, true); 
    i2c_read_blocking(I2C_PORT, lp25hb_addr, buf, len, false);
}

int main() {
    stdio_init_all();

    printf("\nHello, lps25! Reading raw data from registers via i2c...\n");

    // setup i2c
    int f = i2c_init(I2C_PORT, 100 * 1000);
    gpio_set_function(SDA_PIN, GPIO_FUNC_I2C);
    gpio_set_function(SCL_PIN, GPIO_FUNC_I2C);
    gpio_pull_up(SDA_PIN);
    gpio_pull_up(SCL_PIN);

    printf(" hw bus is %d\n",i2c_hw_index(I2C_PORT));
    printf(" frequency is %dHz\n",f);

    f = i2c_set_baudrate (I2C_PORT, 111000);
    printf(" new frequency is %dHz\n",f);

    uint8_t id;
    read_registers(WHO_AM_I, &id, 1);
    printf("\nChip ID is 'probably 0xbd' 0x%x\n", id);

    return 0;
}

 CMakeLists.txtの内容です。


add_executable(lps25hb
        lps25.c
        )

# Pull in our pico_stdlib which pulls in commonly used features
target_link_libraries(lps25hb pico_stdlib hardware_i2c)

# create map/bin/hex file etc.
pico_add_extra_outputs(lps25hb)

 

 pico/works/CMakeLists.txtの内容です。


cmake_minimum_required(VERSION 3.12)

# Pull in SDK (must be before project)
include(pico_sdk_import.cmake)

project(pico_examples C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)

set(PICO_EXAMPLES_PATH ${PROJECT_SOURCE_DIR})

# Initialize the SDK
pico_sdk_init()

# Add blink example
add_subdirectory(cmake)
add_subdirectory(blink)
add_subdirectory(serial)
add_subdirectory(clock)
add_subdirectory(i2cscanner)
add_subdirectory(lps25hb)

 

 ターミナルで、pico/works/buildにおります。

cmake ..

 pico/works/build/lps25hbにおります。

make -j4

 Resetボタンを押したまま、BOOTSELボタンを押し、Resetボタンを離してから、BOOTSELボタンを離します。RPI-RP2ドライブがマウントされました。

 RPI-RP2ドライブへ、lps25hb.uf2をドラッグします。

wakeup
 このセンサは、電源が入ったら、スリープ・モードに遷移します。なので、測定したいときは、wakeupします。
 具体的には、CTRL_REG1レジスタに0x90を書き込みます。

データは3バイト

 LPS25HBの気圧は、PRESS_OUT_H(0x2A)、PRESS_OUT_L(0x29)、PRESS_OUT_XL(0x28)のレジスタを読みだします。ポインタではなく普通のレジスタで連続しているので、0x28から3バイト読み出しますが、0x28 | 0x80のように、MSBを立てないと、正しく読めません。

 読み出した値を4096で割るとhPaの値が求まります。

 修正したlps25.cの内容です。5秒ごとに読み出します。


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

static int lp25hb_addr = 0x5d;

#define I2C_PORT  i2c0
#define WHO_AM_I  0x0f
#define CTRL_REG1 0x20
#define READ_BIT  0x80

#define SDA_PIN 8  // GP8
#define SCL_PIN 9  // GP9

static void read_registers(uint8_t reg, uint8_t *buf, uint16_t len) {
    i2c_write_blocking(I2C_PORT, lp25hb_addr, &reg, 1, true); 
    i2c_read_blocking(I2C_PORT, lp25hb_addr, buf, len, false);
}

static void write_register(uint8_t reg, uint8_t data) {
    uint8_t buf[2];
    buf[0] = reg;
    buf[1] = data;
    i2c_write_blocking(I2C_PORT, lp25hb_addr, buf, 2, true);
}

static int32_t lps25_read_raw() {
    uint8_t buffer[3];
    uint8_t val = 0x28 | READ_BIT;
    i2c_write_blocking(I2C_PORT, lp25hb_addr, &val, 1, true);
    i2c_read_blocking(I2C_PORT, lp25hb_addr, buffer, 3, false);
    int32_t pressure;
    pressure = buffer[2] << 16 | buffer[1] << 8 | buffer[0];
    return pressure;
}

int main() {
    stdio_init_all();

    printf("\nHello, lps25! Reading raw data from registers via i2c...\n");

    // setup i2c
    int f = i2c_init(I2C_PORT, 100 * 1000);
    gpio_set_function(SDA_PIN, GPIO_FUNC_I2C);
    gpio_set_function(SCL_PIN, GPIO_FUNC_I2C);
    gpio_pull_up(SDA_PIN);
    gpio_pull_up(SCL_PIN);

    //  wakeup
    write_register(CTRL_REG1, 0x90);

    while (1) {
        int32_t press = lps25_read_raw();
        printf("Pressure = %.1fhPa\n", press / 4096.0);

        sleep_ms(5000);
    }

    return 0;
}

 実行中の様子です。

 波形を見ます。上の二つがオシロスコープ、下がディジタル信号で、i2cプロトコルをデコードして、値を16進で表示しています。

 lps25_read_raw()関数で、読み出しのレジスタ・アドレスを書き込んだあと3バイトの読み出しを行うとき、

i2c_write_blocking(I2C_PORT, lp25hb_addr, &reg, 1, true);
i2c_read_blocking(I2C_PORT, lp25hb_addr, buf, len, false);

としたので、リピーテッド・スタートになっています。

i2c_write_blocking(I2C_PORT, lp25hb_addr, &reg, 1, false);
i2c_read_blocking(I2C_PORT, lp25hb_addr, buf, len, false);

に変更しましたが、読み出す値に変わりはありませんでした。波形では、ストップ・コンディションになった後スタートしています。

(※)波形は、Analog Discovery Pro ADP3450、WaveForms3.16.3 - April 5th, 2021を使いました。

連載 Raspberry Pi Picoでプログラミング

(1) ラズパイ4の準備(1) USBブートの設定

(2) ラズパイ4の準備(2) 標準入出力の用意

(3) ラズパイ4の準備(3) LチカとHello, world!の実行

(4) ラズパイ4の準備(4) リモート環境の設定

(5) プログラミングの環境整備とLチカ

(6) Hello, World!

(7) 使用するピンと機能

(8) クロックの値の表示

(9) i2cscanner

(10) i2c APIと気圧センサLPS25

(11) i2c 温度センサTMP117

(12) i2c 湿度センサAHT20

(13) spi APIとA-DコンバータMCP3008

(14) spi A-DコンバータMCP3208

(15) gpioファンクション