Raspberry Pi でI2C: C言語プログラミング

C言語でのI2Cデバイスプログラミングについて簡単にまとめました。

LinuxのI2Cデバイスドライバ

個別デバイスドライバ

LinuxでのI2Cデバイスへのアクセスは、それぞれ個別のドライバを作成して行うというのが基本的なポリシーとなっています。
例えば drivers/hwmon/lm75.c などのような形ですね。このドライバの内部で i2c_smbus_read_byte_data() のようなI2Cバス共通I/Fを呼んでいます。

i2c-dev

一方で、直接I2Cバストランザクションを発行するためのi2c-devドライバというものもあります。
こちらは特定のデバイス用ではなく、上に出てきた i2c_smbus_read_byte_data() のような関数を
アプリケーションからドライバI/Fを介して叩けるような機能が提供されます。
これを利用すれば個別のドライバを作成しなくてもとりあえずI2Cデバイスにアクセスすることができます。
このドライバに関しては、Linuxカーネルソースの Documentation/i2c/dev-interface に説明があります。

i2c-devを叩くアプリは、lm-sensorsの i2c-tools に含まれる i2c-dev.h を使用すると
まさにカーネル内の関数を直接叩くような感覚でアプリが構成できます。
(実際はカーネル内と同名のインライン関数が定義されていて、ioctl()を通して対応するカーネル関数が呼ばれる)

また、ioctl()ではなくてread()やwrite()を使ってトランザクションを発行することもできます。

アプリケーションサンプル

ioctl()のラッパーを使う例(i2cget, i2cset)

こちらのエントリで使用したi2c-toolsの i2cget.c i2cset.c のソースがあります。
i2cget.c では例えば i2c_smbus_read_byte_data() などを呼ぶようになっていますが、
これは i2c-dev.h に定義されたインライン関数で、最終的には ioctl() を呼ぶ形になります。

read(), write() を使う例

i2cget, i2cset と似たようなことを行うものを、read(), write() を使って書くと以下のような感じになります。
単純のため、1byteのリード・ライトのみに限定しています。
ここでインクルードしている は、i2c-toolsに含まれるヘッダではなく /usr/include/linux/i2c-dev.h です。

i2cread.c

/*
 *            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
 *                    Version 2, December 2004
 * 
 * Copyright (C) 2013 penkoba
 *
 * Everyone is permitted to copy and distribute verbatim or modified
 * copies of this license document, and changing it is allowed as long
 * as the name is changed.
 *
 *            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
 *   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 *
 *  0. You just DO WHAT THE FUCK YOU WANT TO.
 */

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

int main(int argc, char **argv) 
{
        int fd; 
        unsigned char bus, slave, reg, dat;
        char i2c_dev_fn[64];

        if (argc != 4) { 
                fprintf(stderr, "i2cread <bus> <slave> <reg>\n");
                return 1;
        }

        bus = (unsigned char)strtol(argv[1], NULL, 0);
        slave = (unsigned char)strtol(argv[2], NULL, 0);
        reg = (unsigned char)strtol(argv[3], NULL, 0);

        sprintf(i2c_dev_fn, "/dev/i2c-%d", bus);
        if ((fd = open(i2c_dev_fn, O_RDWR)) < 0) {
                printf("Faild to open i2c port\n");
                return 1;
        }

        if (ioctl(fd, I2C_SLAVE, slave) < 0) {
                printf("Unable to get bus access to talk to slave\n");
                return 1;
        }

        /* write address */
        if ((write(fd, &reg, 1)) != 1) { 
                fprintf(stderr, "Error writing to i2c slave\n");
                return 1;
        }

        /* read data */
        if (read(fd, &dat, 1) != 1) { 
                fprintf(stderr, "Error reading from i2c slave\n");
                return 1;
        }

        printf("%02x\n", dat);

        return 0;
}

i2cwrite.c

/*
 *            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
 *                    Version 2, December 2004
 * 
 * Copyright (C) 2013 penkoba
 *
 * Everyone is permitted to copy and distribute verbatim or modified
 * copies of this license document, and changing it is allowed as long
 * as the name is changed.
 *
 *            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
 *   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 *
 *  0. You just DO WHAT THE FUCK YOU WANT TO.
 */

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

int main(int argc, char **argv) 
{
        int fd;
        unsigned char bus, slave, reg, dat;
        unsigned char buf[2]; 
        char i2c_dev_fn[64];

        if (argc != 5) { 
                fprintf(stderr, "i2cwrite <bus> <slave> <reg> <dat>\n");
                return 1;
        }

        bus = (unsigned char)strtol(argv[1], NULL, 0);
        slave = (unsigned char)strtol(argv[2], NULL, 0);
        reg = (unsigned char)strtol(argv[3], NULL, 0);
        dat = (unsigned char)strtol(argv[4], NULL, 0);

        sprintf(i2c_dev_fn, "/dev/i2c-%d", bus);
        if ((fd = open(i2c_dev_fn, O_RDWR)) < 0) {
                printf("Faild to open i2c port\n");
                return 1;
        }

        if (ioctl(fd, I2C_SLAVE, slave) < 0) {
                printf("Unable to get bus access to talk to slave\n");
                return 1;
        }       

        buf[0] = reg;
        buf[1] = dat;
        if ((write(fd, buf, 2)) != 2) { 
                fprintf(stderr, "Error writing to i2c slave\n");
                return 1;
        }

        return 0;
}

ところでカーネルのドキュメントには
「read()やwrite()ではI2C/SMBusプロトコルのサブセットしか使えないので
ユーザースペースプログラムではほとんど使われない」
と書かれているのですが、もちろん ioctl() でしかできないこともたくさんあるようですが、
任意のサイズの読み書きは read()/write() でしか発行できないので
こちらを使わざるを得ないことも多いような気もするのですが...