フェーズ3

E3-02 レジスタ値の読み出し:WHO_AM_I を読んで、相手が ICM-42688-P であることを確認する

シリーズ:実験シリーズ(フェーズ3)
対応ロードマップ:フェーズ3 / E3-02
この記事で扱う範囲:E3-01 で確認した I2Cアドレス 0x68 に対して、今回は 1つのレジスタを実際に読み、相手が本当に ICM-42688-P であることを確認する。まずは 1バイトだけ正しく読める ところまでを扱う。

1. 目的

今回は、I2Cで接続した IMU から、WHO_AM_I レジスタを1バイト読む ことを行います。
※WHO_AM_Iレジスタとは、ICM-42688-Pの仕様で、ICの識別番号のようなものと理解していれば十分です。

前回の E3-01 では、

  • 0x68 に対して応答がある
  • I2C配線やアドレス条件が大きく崩れていない

ことを確認しました。

ただし、E3-01 の段階では、まだ
「0x68 で誰かが応答している」
ことしか分かっていません。

そこで今回は、ICM-42688-P の WHO_AM_I レジスタ(0x75) を読み、
読めた値が 0x47 であること を確認します。
ICM-42688-P の datasheet では、WHO_AM_I は 75h、既定値は 0x47 とされています。

今回の目的は、
「0x68 に応答する」から一歩進んで、「相手が ICM-42688-P だ」と確認すること です。

2. 前提・環境

2-1. 前提

この記事は、次の内容が終わっている前提で進めます。

  • フェーズ0:ビルド / 書き込み / main到達 / UARTログ出力
  • E2-02:1msごとの時刻を作る
  • E2-03:周期処理
  • E2-05:状態で整理(IDLE / RUN / ERROR)
  • E3-00:I2C配線の“詰まりどころ”を先に潰す
  • E3-01:0x68 に対して応答があるか確認する

2-2. 使用するもの

  • 評価ボード:RTK7EKA8M2S00001BE
  • e² studio + FSP
  • Tera Term
  • IMUモジュール:QCIOT-ICM42688P(PMOD BOARD ICM-42688-P)
  • USB-UART変換ケーブル(またはUSB-UART変換モジュール)
  • ジャンパ線(UART接続用)

2-3. 今回の前提条件

今回は、前回(E3-01)の条件をそのまま使います。

  • 通信方式:I2C
  • 接続方法:QCIOT-ICM42688P の J1 を評価ボードの Pmod1(J26)へ接続
  • 評価ボード側設定:SW4-1 = OFF、SW4-2 = ON
  • AD0:Low(J4 にジャンパキャップあり)
  • I2Cアドレス:0x68

2-4. 今回の動作イメージ

今回は、起動後に次の流れで通信します。

  1. I2Cアドレス 0x68 に対して、読みたいレジスタ番号 0x75 を書く
  2. そのあと 1バイト読む
  3. 読めた値を UART に出す
  4. 値が 0x47 なら成功と判断する

ICM-42688-P の datasheet では、I2Cで内部レジスタを読むときは、
まずレジスタアドレスを書き、その後に読み動作へ進む手順になっています。

今回確認したいのは、
「I2Cで1レジスタ読みができて、その値が 0x47 になること」 です。

3. 今回の変更点

3-1. 配線変更

配線は E3-01 と同じ、下記のままです。

  • QCIOT-ICM42688P の J1 を評価ボードの Pmod1(J26)へ接続
  • 評価ボード側は SW4-1 = OFF、SW4-2 = ON
  • J4 にジャンパキャップあり

3-2. 設定変更

FSP の I2C Master 設定は、E3-01 と同じ下記のままで進めます。

  • 通信方式:I2C Master
  • アドレス幅:7bit
  • 通信速度:100kHz
  • スレーブアドレス:0x68
  • Callback:i2c_master_callback

3-3. コード変更

今回は次の処理を追加します。

  • WHO_AM_I レジスタ番号 0x75 を送る
  • 続けて 1バイト読む
  • 読み出した値を UART に出す
  • 値が 0x47 かどうかで成功/失敗を判定する

前回は「応答があるか」でしたが、今回は
「どのレジスタを読むか」まで指定して、戻ってきた値を見るところが違いです。

4. 手順

4-1. E3-01 の条件どおりに接続する

まず、E3-01と同じ、下記の条件どおりに接続します。

  • QCIOT-ICM42688P の J1 を評価ボードの Pmod1(J26)へ接続
  • SW4-1 = OFF
  • SW4-2 = ON
  • J4 にジャンパキャップあり

4-2. E3-01 の I2C Master 設定が入ったプロジェクトをベースにする

今回は、E3-01 で 0x68 応答確認までできたプロジェクトをそのまま使います。
つまり、

  • UARTログが出せる
  • I2C Master が Open できる
  • 0x68 に対して通信できる

状態から始めます。

4-3. USB-UARTケーブルのUARTと評価ボードを接続する

E3-01と同じように、Tera Term で UART ログを見られる状態にします。

4-4. WHO_AM_I レジスタ番号を確認する

今回読むレジスタは、WHO_AM_I です。
ICM-42688-P の データシート では、

  • WHO_AM_I のアドレス:0x75
  • 既定値:0x47

となっています。

今回は、

  • 読みたいレジスタ番号:0x75
  • 期待する値:0x47

として進めます。

4-5. コードを追加する

今回は、次の流れになるようにコードを追加します。

  1. UART を Open
  2. I2C Master を Open
  3. スレーブアドレスを 0x68 に設定
  4. WHO_AM_I レジスタ番号 0x75 を送る
  5. 1バイト読む
  6. 読んだ値を UART に出力する
  7. 0x47 なら成功ログ、違えば失敗ログを出力する

5. コード

※下記は考え方を分かりやすくするためのシンプルな例です。
※ I2Cインスタンス名、UART送信関数名、コールバックの有無は、自分のプロジェクトに合わせて置き換えてください。
※ FSPの使い方や環境差によっては、関数名や引数の細かい部分を調整してください。

#include "hal_data.h"
#include <stdio.h>
#include <string.h>
#include <stdbool.h>

#define IMU_I2C_ADDR          (0x68U)
#define IMU_REG_WHO_AM_I      (0x75U)
#define IMU_WHO_AM_I_EXPECTED (0x47U)
#define I2C_TIMEOUT_COUNT     (100000U)

static volatile bool g_i2c_done  = false;
static volatile bool g_i2c_error = false;

static void uart_print(const char * p_text);
static bool imu_read_register_1byte(uint8_t reg_addr, uint8_t * p_value);

void i2c_master_callback(i2c_master_callback_args_t * p_args)
{
    if (NULL == p_args)
    {
        return;
    }

    switch (p_args->event)
    {
        case I2C_MASTER_EVENT_ABORTED:
        {
            g_i2c_error = true;
            g_i2c_done  = true;
            break;
        }

        case I2C_MASTER_EVENT_RX_COMPLETE:
        case I2C_MASTER_EVENT_TX_COMPLETE:
        {
            g_i2c_error = false;
            g_i2c_done  = true;
            break;
        }

        default:
        {
            break;
        }
    }
}

void hal_entry(void)
{
    fsp_err_t err;
    uint8_t who_am_i = 0U;
    char msg[64];

    /* UART Open */
    err = R_SCI_B_UART_Open(&g_uart0_ctrl, &g_uart0_cfg);
    if (FSP_SUCCESS != err)
    {
        while (1)
        {
            ;
        }
    }

    uart_print("WHO_AM_I read start\r\n");

    /* I2C Open */
    err = R_IIC_MASTER_Open(&g_i2c_master0_ctrl, &g_i2c_master0_cfg);
    if (FSP_SUCCESS != err)
    {
        uart_print("I2C open error\r\n");
        while (1)
        {
            ;
        }
    }

    err = R_IIC_MASTER_SlaveAddressSet(&g_i2c_master0_ctrl,
                                       IMU_I2C_ADDR,
                                       I2C_MASTER_ADDR_MODE_7BIT);
    if (FSP_SUCCESS != err)
    {
        uart_print("Slave address set error\r\n");
        while (1)
        {
            ;
        }
    }

    if (imu_read_register_1byte(IMU_REG_WHO_AM_I, &who_am_i))
    {
        snprintf(msg, sizeof(msg), "WHO_AM_I = 0x%02X\r\n", who_am_i);
        uart_print(msg);

        if (IMU_WHO_AM_I_EXPECTED == who_am_i)
        {
            uart_print("ICM-42688-P detected\r\n");
        }
        else
        {
            uart_print("Unexpected WHO_AM_I value\r\n");
        }
    }
    else
    {
        uart_print("WHO_AM_I read error\r\n");
    }

    while (1)
    {
        ;
    }
}

static bool imu_read_register_1byte(uint8_t reg_addr, uint8_t * p_value)
{
    fsp_err_t err;
    uint32_t timeout;

    if (NULL == p_value)
    {
        return false;
    }

    /* 1) 読みたいレジスタ番号を書き込む */
    g_i2c_done  = false;
    g_i2c_error = false;

    err = R_IIC_MASTER_Write(&g_i2c_master0_ctrl, ®_addr, 1U, true);
    if (FSP_SUCCESS != err)
    {
        return false;
    }

    timeout = I2C_TIMEOUT_COUNT;
    while ((false == g_i2c_done) && (timeout > 0U))
    {
        timeout--;
    }

    if ((0U == timeout) || (true == g_i2c_error))
    {
        return false;
    }

    /* 2) 1バイト読む */
    g_i2c_done  = false;
    g_i2c_error = false;

    err = R_IIC_MASTER_Read(&g_i2c_master0_ctrl, p_value, 1U, false);
    if (FSP_SUCCESS != err)
    {
        return false;
    }

    timeout = I2C_TIMEOUT_COUNT;
    while ((false == g_i2c_done) && (timeout > 0U))
    {
        timeout--;
    }

    if ((0U == timeout) || (true == g_i2c_error))
    {
        return false;
    }

    return true;
}

static void uart_print(const char * p_text)
{
    fsp_err_t err;

    err = R_SCI_B_UART_Write(&g_uart0_ctrl, (uint8_t *) p_text, strlen(p_text));
    if (FSP_SUCCESS != err)
    {
        while (1)
        {
            ;
        }
    }

    for (volatile uint32_t i = 0; i < 1000000U; i++)
    {
        __asm volatile ("nop");
    }
}

6. 実行結果

6-1. 確認したい動き

今回の実験では、次のようなログになればOKです。

WHO_AM_I read start  
WHO_AM_I = 0x47  
ICM-42688-P detected

このログが出れば、

  • I2Cアドレス 0x68 で通信できている
  • レジスタ 0x75 を読めている
  • 読めた値が 0x47
  • 相手が ICM-42688-P である

と判断できます。WHO_AM_I は ICM-42688-P の identity 確認用レジスタで、既定値は 0x47 です。

6-2. 今回確認できればよいこと

今回の段階では、次が確認できれば十分です。

  • 1レジスタ読み ができる
  • 読みたいレジスタ番号を指定して値を受け取れる
  • 値が 0x47 になり、相手確認までできる

7. ハマりポイント/原因と対策

7-1. WHO_AM_I read error になる

原因

  • 0x68 での接続確認がまだ安定していない
  • レジスタ番号を書いたあとに、読み動作へ正しく進めていない
  • 送信完了・受信完了の待ち方がうまくいっていない
  • タイムアウトが短すぎて、終わる前に失敗扱いになっている

対策

  • まず E3-01 の ACK at 0x68 が安定して出る状態に戻す
  • 「レジスタ番号を書いてから読む」という2段階になっているか見直す
  • コールバックで TX_COMPLETE / RX_COMPLETE を受けているか確認する
  • まずは 1バイト読みだけに絞り、余分な処理を入れない

7-2. WHO_AM_I = 0x000xFF になる

原因

  • 読み動作は走っているが、正しいレジスタの値を取れていない
  • レジスタ番号 0x75 を正しく送れていない
  • 読み取る先の変数やバッファの使い方が崩れている
  • 「書く→読む」の切り替えが想定どおりになっていない

対策

  • まず reg_addr = 0x75 が正しく設定されているか確認する
  • 読み取るバッファは 1バイトだけにして、処理を最小にする
  • E3-01 のコードから大きく作り変えすぎず、必要最小限の追加にとどめる
  • まずは WHO_AM_I だけ読み、複数バイト読みは次回以降に回す

7-3. Unexpected WHO_AM_I value になる

原因

  • 通信相手が想定したIMUではない
  • アドレス条件や接続条件が違っている
  • 1バイト読みはできているが、読んでいるレジスタ番号がずれている
    対策
  • WHO_AM_I の期待値が 0x47 になっているか確認する
  • レジスタ番号が 0x75 になっているか確認する
  • 0x68 の相手が本当に QCIOT-ICM42688P か、配線とモジュールをもう一度確認する
  • まずは「0x68応答確認 → WHO_AM_I 読み」の順で切り分ける

7-4. I2C open errorSlave address set error になる

原因

  • FSP設定が不足している、または設定内容が合っていない
  • Generate Project Content 後のコード更新ができていない
  • I2Cインスタンス名や設定名がコードと一致していない

対策

  • g_i2c_master0 などの名前が生成コードと一致しているか確認する
  • FSP設定保存後に Generate Project Content を実行する
  • ビルドエラーがなくても、設定変更がコードへ反映されているか見直す

8. 今回わかったこと

今回の実験で大事なのは、
I2Cでは「応答がある」だけで終わらず、WHO_AM_I のような識別用レジスタを読むと問題なく接続できていることがはっきりする
ということです。

9. 次回やること

E3-03 連続読み(複数バイト):加速度やジャイロの元データにつながる読み方を確認する

10. 関連リンク

  • 実験シリーズ:E2-02 タイマで1ms毎にカウントアップするストップウォッチを作る(リンク)
  • 実験シリーズ:E2-03 タイマで周期処理(リンク)
  • 実験シリーズ:E2-05 状態で整理(IDLE / RUN / ERROR)(リンク)
  • 実験シリーズ:E3-00 I2C配線の“詰まりどころ”を先に潰す(リンク)
  • 実験シリーズ:E3-01 I2C接続確認:0x68 に対して応答があるか確認する(リンク)
  • 基礎シリーズ:電気回路 はじめに読む(リンク)
  • 基礎シリーズ:電子回路 はじめに読む(リンク)
  • 基礎シリーズ:GNDとは?なぜ基準点が必要なのか?(リンク)
  • 基礎シリーズ:マイコン(RA8M2) はじめに読む(リンク)

-フェーズ3