Raspberry PiでI2Cを利用する方法 [i2c-dev][C言語]

今回はRaspberry PiにてC言語でI2Cを利用する方法をご紹介したいと思います。

Raspberry PiでI2Cを使う手段としてはpigpioWiringPiといった手段が存在しますが、今回はLinuxでネイティブサポートされているi2c-devというデバイスドライバを使います。

i2c-devであればRaspberry Pi以外のLinuxデバイス(Jetson等)でも汎用的に使えるというメリットがあり、使い方もかなりシンプルなので是非押さえておきたいところです。

また、組み込みシステムではC言語を使用することが多く、今回はC言語での利用を想定して解説を行います。

それでは本編いってみましょう。

目次

Raspberry Piの設定

Raspberry Piの設定をしていきます。
Raspberry PiはカーネルにI2Cドライバが用意されているので、configから有効にしてあげます。

以下のコマンドを実行してください。

$ sudo raspi-config

config画面が立ち上がるので、「3 Interface Options」を選択してエンターを押してください。

「P5 I2C」を選択してエンター。

<はい>を選択してエンターを押せば完了です。
リブートするか聞かれたら<はい>でリブートしてください。

リブート後、I2Cが有効になっているか確認するため下記コマンドを実行してください。

$ ls /dev/*i2c*

下記が表示されれば問題なくI2Cが認識されています。(末尾の数字は異なる可能性があります)

/dev/i2c-1

追加でツールをインストールしておきます。下記コマンドを実行してください。

$ sudo apt install -y i2c-tools

i2cdetectでデバイスが接続できているか確認できます。
I2CデバイスをRaspberry Piに接続して下記コマンドを実行してください。

$ i2cdetect -y 1

正しく接続できていれば以下のように該当するデバイスのアドレスがアクティブになって表示されます。
この例ではスレーブアドレスが0x76、0x3cのデバイスが見えていることになります。

     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- 3c -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- 76 --

ここまで確認できればドライバの有効化と部品との接続はOKです。

I2Cドライバ(i2c-dev)の制御方法

i2c-devは2通りの使い方があります。

  1. I2C_SLAVEを指定してread()とwrite()で手軽に操作
  2. I2C_RDWRを指定して詳細にメッセージパケットをカスタマイズ

①のやり方では、最初にioctlでスレーブアドレスを設定する必要があります。最初に設定した宛先スレーブアドレスに、read(), write()がなされるという仕組みです。
②ではi2c_msgという構造体を生成し、その中でスレーブアドレスを指定することでやり取りを行います。②の方がいろいろ応用が効くので、今回は②を紹介します。

i2c-devはいくつかの作法を押さえれば簡単に使うことができる便利なデバイスドライバです。
それではサンプルプログラムを掲載しておきます。

/* -----------------------------------------------------------------------------
 Include
------------------------------------------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>

#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>

typedef char		C08;
typedef unsigned char BOOL;
typedef unsigned char U08;
typedef unsigned short U16;
typedef unsigned long U32;
typedef unsigned long long U64;
typedef char S08;
typedef short S16;
typedef long S32;
typedef long long S64;
typedef float F32;
typedef double D64;

/* -----------------------------------------------------------------------------
 Define
------------------------------------------------------------------------------*/
#define I2C_DEVICE ("/dev/i2c-1")
#define FD_INIT_VAL (0xFFFFFFFF)

/* -----------------------------------------------------------------------------
 Global
------------------------------------------------------------------------------*/
static S32 fd = FD_INIT_VAL;

/* -----------------------------------------------------------------------------
 Function   : I2C Init
 Memo       : I2Cデバイスのオープンを行う。プロセス終了するまでオープンしたままにする
 Date       : 2021.08.28
------------------------------------------------------------------------------*/
BOOL I2cCtl_Init()
{
    BOOL status = OK;

    /* デバイスオープン */
    if (fd == (S32)FD_INIT_VAL)
    {
        fd = open(I2C_DEVICE, O_RDWR);
        if (fd < 0)
        {
            perror(I2C_DEVICE);
            return NG;
        }
    }

    return status;
}

/* -----------------------------------------------------------------------------
 Function   : I2C Write
 Memo       : メッセージの先頭に書き込み先レジスタを入れて1メッセージとする
 Date       : 2021.08.28
------------------------------------------------------------------------------*/
BOOL I2cCtl_Write(U08 dev_adr, void *buf, U32 buf_length)
{
    struct i2c_msg msg;
    struct i2c_rdwr_ioctl_data packets;
    S32 ret;
    BOOL status = OK;

    /* メッセージ作成 */
    msg.addr = dev_adr;
    msg.flags = 0;
    msg.len = buf_length;
    msg.buf = buf;

    /* Send */
    packets.msgs = &msg;
    packets.nmsgs = 1; //msgのサイズ
    ret = ioctl(fd, I2C_RDWR, &packets);
    if(ret < 0)
    {
        perror(I2C_DEVICE);
        return NG;
    }

    return status;
}

/* -----------------------------------------------------------------------------
 Function   : I2C Read
 Memo       : メッセージの先頭に書き込み先レジスタを入れて1メッセージとする
 Date       : 2021.08.28
------------------------------------------------------------------------------*/
BOOL I2cCtl_Read(U08 dev_adr, void *buf, U32 buf_length)
{
    struct i2c_msg msg;
    struct i2c_rdwr_ioctl_data packets;
    S32 ret;
    BOOL status = OK;

     /* メッセージ作成(Data Read) */
    msg.addr = dev_adr;
    msg.flags = I2C_M_RD;
    msg.len = buf_length;
    msg.buf = buf;

    packets.msgs = &msg;
    packets.nmsgs = 1; //msgのサイズ
    ret = ioctl(fd, I2C_RDWR, &packets);
    if(ret < 0)
    {
        perror(I2C_DEVICE);
        return NG;
    }

    return status;
}

解説

それでは個別にソースコードの説明をしていきたいと思います。

初期化

/* -----------------------------------------------------------------------------
 Define
------------------------------------------------------------------------------*/
#define I2C_DEVICE ("/dev/i2c-1")

/* -----------------------------------------------------------------------------
 Function   : I2C Init
 Memo       : I2Cデバイスのオープンを行う。プロセス終了するまでオープンしたままにする
 Date       : 2021.08.28
------------------------------------------------------------------------------*/
BOOL I2cCtl_Init()
{
    BOOL status = OK;

    /* デバイスオープン */
    if (fd == (S32)FD_INIT_VAL)
    {
        fd = open(I2C_DEVICE, O_RDWR);
        if (fd < 0)
        {
            perror(I2C_DEVICE);
            return NG;
        }
    }

    return status;
}

初期化部では、デバイスドライバのオープンをしています。
デバイスドライバは/dev/以下に見えているモジュールになりますが、今回のi2c-devに関しては/dev/i2c-1ということになります。
環境によってはi2c-2やi2c-3かもしれませんので、下記コマンドでご自身で確認してみてください。

ls /dev/i2c*

もしi2c-1以外の数値の場合は、ソースコード上のI2C_DEVICEの定義を修正してください。

Write(通信相手へのデータ送信) / Read(通信相手からデータ読み出し)

/* -----------------------------------------------------------------------------
 Function   : I2C Write
 Memo       : メッセージの先頭に書き込み先レジスタを入れて1メッセージとする
 Date       : 2021.08.28
------------------------------------------------------------------------------*/
BOOL I2cCtl_Write(U08 dev_adr, void *buf, U32 buf_length)
{
    struct i2c_msg msg;
    struct i2c_rdwr_ioctl_data packets;
    S32 ret;
    BOOL status = OK;

    /* メッセージ作成 */
    msg.addr = dev_adr;
    msg.flags = 0;
    msg.len = buf_length;
    msg.buf = buf;

    /* Send */
    packets.msgs = &msg;
    packets.nmsgs = 1; //msgのサイズ
    ret = ioctl(fd, I2C_RDWR, &packets);
    if(ret < 0)
    {
        perror(I2C_DEVICE);
        return NG;
    }

    return status;
}

/* -----------------------------------------------------------------------------
 Function   : I2C Read
 Memo       : メッセージの先頭に書き込み先レジスタを入れて1メッセージとする
 Date       : 2021.08.28
------------------------------------------------------------------------------*/
BOOL I2cCtl_Read(U08 dev_adr, void *buf, U32 buf_length)
{
    struct i2c_msg msg;
    struct i2c_rdwr_ioctl_data packets;
    S32 ret;
    BOOL status = OK;

     /* メッセージ作成(Data Read) */
    msg.addr = dev_adr;
    msg.flags = I2C_M_RD;
    msg.len = buf_length;
    msg.buf = buf;

    packets.msgs = &msg;
    packets.nmsgs = 1; //msgのサイズ
    ret = ioctl(fd, I2C_RDWR, &packets);
    if(ret < 0)
    {
        perror(I2C_DEVICE);
        return NG;
    }

    return status;
}

i2cドライバの制御にあたってはioctl経由でデバイスドライバとデータのやりとりを行います。
ioctlで渡すデータはi2c_rdwr_ioctl_dataという構造体になっており、そのメンバ変数としてi2c_msgを記述することになります。

i2c_msgのaddrには通信相手のスレーブアドレスを指定します。

flagsにはRead(通信相手の部品からデータを読み出す)かWrite(通信相手の部品にデータを書き込む)を指定します。
Read時はI2C_M_RD、Write時は0を設定します。

lenには書き込むバイト数、もしくは読み出すバイト数、
bufには書き込むデータバッファ、もしくは読みだしたデータを格納するバッファのアドレスを指定します。

i2c_rdwr_ioctl_dataのmsgsに上述のi2c_msgを格納し、nmsgsに送るi2c_msgの数を指定します(サンプルコードでは1固定になっています)。

最後に

いかがでしたでしょうか。

今回紹介したi2c-devは非常にシンプルにかつ自由度高くI2Cを制御できるデバイスドライバですので、使い方を理解していろいろ便利かと思います。

もしこの記事を気に入って頂けたらシェアしていただけると幸いです。

それでは。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次