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) |
指定されたバイト数をアドレスに書き込む。 |
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, ®, 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, ®, 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, ®, 1, true);
i2c_read_blocking(I2C_PORT, lp25hb_addr, buf, len, false);
としたので、リピーテッド・スタートになっています。
i2c_write_blocking(I2C_PORT, lp25hb_addr, ®, 1, false);
i2c_read_blocking(I2C_PORT, lp25hb_addr, buf, len, false);
に変更しましたが、読み出す値に変わりはありませんでした。波形では、ストップ・コンディションになった後スタートしています。
(※)波形は、Analog Discovery Pro ADP3450、WaveForms3.16.3 - April 5th, 2021を使いました。