起動時にブラウザをフルスクリーンモードで表示する

今回やってみたことを、まずは結果からお見せします。左がraspbianのデフォルトのウィンドウマネージャからepiphany(2014-09-09版wheezyにデフォルトでインストールされているブラウザ)を起動した状態、右がchromiumだけをフルスクリーン表示させたものです。
GUIとして使える空間がこれだけ変わってきます。単機能アプリケーションを構築したい場合は、これは便利ですね。


参照したサイト

"raspberry pi キオスク" で検索するといろいろ出てくるのですが、以下のサイトが端的にまとまっていました。
http://plaza.rakuten.co.jp/kugutsushi/diary/201404200000/

ブラウザ (chromium)

ブラウザは、chromiumのapp/kioskモードが便利なのでそれを使うことにします。
また、マウスポインタを表示したくない場合はunclutterというツールを使用します。

% sudo apt-get install chromium x11-xserver-utils unclutter

chrome/chromiumの起動オプションは以下。
http://chrome.half-moon.org/43.html#a05d2b6e
アドレスバーなど余計なものを表示しないモードで起動するオプションが2種類あるようなのですが、
chromium --app=

chromium --kiosk
がどう違うかよくわかりません。ぱっと見は同じです。ここでは --app の方を使ってみました。

.xinitrc

次にXセッションの設定ですが、ブラウザだけ起動すればよいのでウィンドウマネージャの起動は不要です。

~/.xinitrc を以下の内容で作成しました。んー、シンプル。

#!/bin/sh

# turn off mouse pointer
#unclutter -idle 15 -root &

xset -dpms
xset s off
chromium --app=<URL> --window-size=320,240

ブラウザの履歴を消したりとか、シャットダウンをせずに電源ぶち切りに対応したりしようとすると起動時の処理にいろいろ追加する必要が出てきますが、とりあえずはこんなところで。

タッチパネルつきTFTを使ってみた

Raspberry Piの画面出力については、手持ちのUSBモニターを使ったりvncで他のデバイスに出してみたり といくつか試してみましたが、最近では専用の液晶モジュールが普通に手に入る状況になりましたので購入してみました。

Raspberry Pi用2.8インチ TFTモニタ特別セット

Raspberry Pi用2.8インチ TFTモニタ特別セット


タクトスイッチ以外はアセンブル済の商品で、Raspberry Piのコネクタにそのまま挿すだけです。

ドライバのインストールは以下のインストラクションに従うだけですが、いつものことですが apt-get update は事前にやっておきましょう。
https://learn.adafruit.com/adafruit-pitft-28-inch-resistive-touchscreen-display-raspberry-pi/easy-install (英語)
http://store.techshare.jp/html/page62.html (日本語)

インストールが済んでリブートしたら準備OKです。
自動でpiユーザーでXを起動する設定にしている場合は、そのままTFTにXの画面が出ると思います。

私の場合はコンソールからログインする設定で、その上普段はpi以外の別ユーザーで使っているのですが、いつものようにログインして startx しても内蔵ビデオ(HDMIとか)の方に出てしまいます。
TFTの方に出すには

export FRAMEBUFFER=/dev/fb1

してから startx する必要があります。

解像度はQVGA(320x240)なので多少窮屈ですが、コンパクトで一体感のあるディスプレイがあればアプリケーションのイメージも膨らみます。
下はブラウザでグラフを表示してみた例です。

nodeあたりでいろいろやってみたくなりますね。

Raspberry Pi で赤外線リモコン

ようやく、というかずっとやる予定でやっていなかった Raspberry Piを赤外線リモコンにする というのをやってみました。

参照:http://homebrew.jp/show?page=1480

ハードウェア

パーツ

パーツは "Arduinoでリモコン" で使ったのと同じ物を使います。

秋月で購入しました。
この他に抵抗、トランジスタが必要です。

接続

以下の図のような感じで回路を組みました。

電源については注意が必要で、 http://elinux.org/RPi_Low-level_peripherals によると3.3Vから供給できるのは50mAまで、とのことですがLEDには50mA(最大100mAの半分)流す計算で行くので、他のデバイスもあるので足りません。また、複数のLEDを接続する場合は余計にです。なので、LEDは5Vで駆動します。ちなみに5V端子は他とショートさせると壊れる可能性が高いので注意してください。
一方、GPIOは5V耐圧ではありませんので、受信モジュールは3.3Vで駆動します。

LEDの直列抵抗は50mA流すとして (5-1.35) / 0.05 = 73(Ω) なので、これに近い値のものを使います。

配線は少し整理してこんな感じにしてあります。

LED拡散キャップ

使用している赤外線LEDは、照射方向に非常にシビア(データシートによると半減角15度:中心から7.5度)で、ちょっとずれただけで全然反応しなくなるので実際に使おうとすると位置決めが面倒です。
そこで、LED拡散キャップというのを使用してみました(上の写真はつけていない状態です)。

すると、距離さえ近ければ角度にはかなり寛容になり、1m程度の距離で30度くらいずれてても反応するようになりました。距離的にはまだ改善の余地がありますが、近くにさえ置いておけばテレビ+レコーダーなど複数機器のコントロールが無理なく可能となります。

ソフトウェア

LIRC

LIRCという、Linuxで赤外線リモコンを使用するためのソフトウェアがありますのでこれを使用します。
http://www.lirc.org/

$ sudo apt-get install lirc

ちなみに、エアコンはリモコン信号が長いためLIRCで扱えない可能性が高いです(機種によると思いますが)。具体的にはLIRCで扱えるのは64bitまでですが、うちで使用しているエアコンの信号は80bitとか144bitだったりしています。

カーネルモジュール

Raspbianには必要なモジュールがすでに入っていますが、私はkernel(3.11.y)を自前でコンパイルしており、configを3.6.11のからmake oldconfigした関係で必要なモジュールがコンパイルされていませんでした。CONFIG_LIRC_RPI が必要となるのですが、configメニューの階層は以下です。

Device Drivers --->
  Staging Drivers --->
    Media staging drivers --->
      Linux Infrared Remote Control IR receiver/transmitter drivers --->
        Homebrew GPIO Port Receiver/Transmitter for the RaspberryPi

入出力に使うGPIO番号を指定してモジュールをロードします。(依存関係でlirc-devモジュールもロードされます)

$ sudo modprobe lirc-rpi gpio_in_pin=24 gpio_out_pin=25

恒久的な設定にする場合は、/etc/modprobe.d/lirc.conf を以下の内容で作成します。

options lirc-rpi gpio_in_pin=24 gpio_out_pin=25

モジュールのロード自体はlircdの起動時に自動で行われますので、 /etc/modules への追加は不要です。

バイスファイルができていることを確認

$ ls  -l /dev/lirc*
crw-rw---T 1 root video 249, 0 12月 23 13:23 /dev/lirc0

debugfsで、GPIOのアサインを確認(これは知りませんでした)

% sudo mount -t debugfs debugfs /sys/kernel/debug
% sudo cat /sys/kernel/debug/gpio
GPIOs 0-53, bcm2708_gpio:
 gpio-16  (led0                ) out hi
 gpio-24  (lirc_rpi ir/in      ) in  hi
 gpio-25  (lirc_rpi ir/out     ) in  lo
受信動作確認

何でもよいので赤外線リモコンを用意し、mode2を使って信号が受信できているかどうか確認します。

% mode2 -d /dev/lirc0
(リモコンを赤外線受信モジュールに向けて何かボタンを押す)
space 3809471
pulse 2370
space 602
pulse 1211
space 583
pulse 588
space 599
pulse 1204
space 584
pulse 594
 :

このような反応があればOKです。

lircdのconfig作成

リモコンコマンドを発信する準備として、irrecordを使ってリモコン信号を学習し、lircdのconfigを作成します。最終的には必要なボタンについて全て学習する必要があるので結構面倒です。
コマンド中に要求されるステップは3つあります。

  • いろんなボタンを1秒くらいずつ押す
  • 記録する各ボタンに名前をつけて、学習する
  • ボタンを短く繰り返し押す

以下はソニーのテレビリモコンを学習させた例です。

% irrecord -n -d /dev/lirc0 tv.conf
irrecord -  application for recording IR-codes for usage with lirc

Copyright (C) 1998,1999 Christoph Bartelmus(lirc@bartelmus.de)

 :

Please send the finished config files to  so that I
can make them available to others. Don't forget to put all information
that you can get about the remote control in the header of the file.

Press RETURN to continue.
(リターンを押す)


Now start pressing buttons on your remote control.

It is very important that you press many different buttons and hold them
down for approximately one second. Each button should generate at least one
dot but in no case more than ten dots of output.
Don't stop pressing buttons until two lines of dots (2x80) have been
generated.

Press RETURN now to start recording.

(リターンを押す)
(リモコンを受信モジュールに向け、いろんなボタンを1秒くらいずつ押す)
................................................................................
Found const length: 44706
Please keep on pressing buttons like described above.
................................................................................
Space/pulse encoded remote control found.
Signal length is 25.
Found possible header: 2359 629
Found trail pulse: 582
No repeat code found.
Signals are pulse encoded.
Signal length is 12
Now enter the names for the buttons.

(以降、学習するボタンの名前を入力し、ボタンを押していく)
Please enter the name for the next button (press  to finish recording)
power

Now hold down button "power".
(リモコンの電源ボタンを押す)

Please enter the name for the next button (press  to finish recording)
1

Now hold down button "1".
(リモコンの1chボタンを押す)

 :

Please enter the name for the next button (press  to finish recording)
(リターンを押す)

Checking for toggle bit mask.
Please press an arbitrary button repeatedly as fast as possible.
Make sure you keep pressing the SAME button and that you DON'T HOLD
the button down!.
If you can't see any dots appear, then wait a bit between button presses.

Press RETURN to continue.
(リモコンの任意のボタンを繰り返し押す)
.....................
No toggle bit mask found.
Successfully written config file.

以下のtv.confが生成されました。

# Please make this file available to others
# by sending it to <lirc@bartelmus.de>
#
# this config file was automatically generated
# using lirc-0.9.0-pre1(default) on Mon Dec 23 13:48:00 2013
#
# contributed by
#
# brand:                       tv.conf
# model no. of remote control:
# devices being controlled by this remote:
#

begin remote

  name  tv.conf
  bits           12
  flags SPACE_ENC|CONST_LENGTH
  eps            30
  aeps          100

  header       2359   600
  one          1167   616
  zero          575   616
  gap          44706
  min_repeat      2
#  suppress_repeat 2
#  uncomment to suppress unwanted repeats
  toggle_bit_mask 0x0

      begin codes
          power                    0xA90
          1                        0x010
          2                        0x810
          3                        0x410
          4                        0xC10
          5                        0x210
          6                        0xA10
          7                        0x610
          8                        0xE10
          9                        0x110
          10                       0x910
          11                       0x510
          12                       0xD10
      end codes

end remote
リモコンフォーマットに関する話

少し本筋から離れますが、リモコンのフォーマットに関して確認しておきます。

ここでは(簡単のためもありますが)電源とチャンネルボタンしか学習しなかったのですが、今時のリモコンはボタンがたくさんあってこれがLIRCにとっては多少ややこしいことを引き起こします。例えば「番組表」ボタンを含めて学習すると、記録されるフォーマットは全く違ったものになります。詳細は別エントリで試していきたいと思いますが、ボタンによってコマンド長が異なることが原因です。

ここで学習したコマンドはすべて12bitで、SONYフォーマットで 5bitのプロダクトIDと7bitのコマンドからなります。一般的にリモコン信号はLSBファーストで送信されるのですが、上記confファイルに記録されているのはLSBファーストの信号をそのままの順番で表現したものになります。これを逆順(MSBファースト)にすると以下のようになりますが、"SONYテレビのリモコンコマンド" で書いたものと一致します。リモコンコードを表現する場合はこちらの方が普通だと思います。

power: 0x095
1:     0x080
2:     0x081
3:     0x082
4:     0x083
5:     0x084
6:     0x085
7:     0x086
8:     0x087
9:     0x088
10:    0x089
11:    0x08a
12:    0x08b
lircdの設定と起動

生成された tv.conf を以下のように少し修正を入れてから /etc/lirc/lircd.conf として置きます。

# brand:                       SONY
# model no. of remote control: RM-JD018
  name  BRAVIA

ちなみに、複数のリモコンの信号を登録する場合は生成した複数のconfファイルを単純につなげればOKです。

次に、/etc/lirc/hardware.conf を以下のように編集します。

# /etc/lirc/hardware.conf
#
# Arguments which will be used when launching lircd
LIRCD_ARGS="--uinput"

#Don't start lircmd even if there seems to be a good config file
#START_LIRCMD=false

#Don't start irexec, even if a good config file seems to exist.
#START_IREXEC=false

#Try to load appropriate kernel modules
LOAD_MODULES=true

# Run "lircd --driver=help" for a list of supported drivers.
DRIVER="default"
# usually /dev/lirc0 is the correct setting for systems using udev
DEVICE="/dev/lirc0"
MODULES="lirc_rpi"

# Default configuration files for your hardware if any
LIRCD_CONF=""
LIRCMD_CONF=""

lircd起動

% sudo /etc/init.d/lirc start

自動で起動する場合は、update-rc.dで登録します。

% sudo update-rc.d lirc defaults
lircdを利用したコマンドの実行

irsendで登録されているコマンドの確認や発信ができます。

リモコンとコマンドの確認

% irsend LIST '' ''
irsend: BRAVIA
% irsend LIST BRAVIA ''
irsend: 0000000000000a90 power
irsend: 0000000000000010 1
irsend: 0000000000000810 2
irsend: 0000000000000410 3
irsend: 0000000000000c10 4
irsend: 0000000000000210 5
irsend: 0000000000000a10 6
irsend: 0000000000000610 7
irsend: 0000000000000e10 8
irsend: 0000000000000110 9
irsend: 0000000000000910 10
irsend: 0000000000000510 11
irsend: 0000000000000d10 12

発信

% irsend SEND_ONCE BRAVIA power

テレビの電源が入りました。

また、irwで学習したコマンドを受信して認識することもできます。

% irw
(リモコンの電源ボタン押す)
0000000000000a90 00 power BRAVIA
0000000000000a90 01 power BRAVIA
0000000000000a90 02 power BRAVIA

まとめ

基本的な使い方ができるところまでひと通りやってみましたが、やはり実物の家電を操作できるのは楽しいです。あとはいろんなリモコンで試したり、ブラウザからのコントロール音声認識、逆にリモコンでRaspberry Piを操作したりとかの応用編もいろいろやってみたいですね。

Raspberry Pi でI2C: キャラクタLCD

秋月のACM1602NI-FLW-FBW-M01を使ったキャラクタLCDモジュールを購入しました。
Raspberry Piに接続するも、当初思ったように動かなくて困っていたのですが こちらのページ に答えがあり、
要するにモジュールのPICマイコンの処理が間に合っていないという残念な状態のようです。
I2Cクロックを下げてとりあえず動いたので、メモっときます。

接続

LCDモジュールのコネクタは7ピンありますが、Raspberry Pi 側からは VDD(3.3V), GND, SDL, SDA を引っ張ってくればOKです。
コントラスト(V0)は調整可能にしておいた方がいいですけど、0.3Vくらいを入れておけばとりあえず丁度いいです。


注意点

上記ページに書かれている通り、このLCDを使うにはいくつかの注意点があります。

  • リードアクセスしない(i2c-toolsのi2cdetectとかもダメ)
  • I2Cクロックを50kHzくらいに落とす

以上を踏まえ、進めます。

ドライバのロード

i2c-bcm2708ドライバのオプションに転送レートを指定してロードします。
i2c関係のドライバがロードされている場合は、一旦アンロード

% sudo rmmod i2c-dev i2c-bcm2708

I2Cクロックを50kHzに指定してモジュールをロード

% sudo modprobe i2c-bcm2708 baudrate=50000
% sudo modprobe i2c-dev

これを恒久的な設定とする場合は、/etc/modprobe.d/i2c.conf として以下の内容で作成します。

options i2c_bcm2708 baudrate=50000

設定とデータ送信

i2c-toolsを使用して表示テストをしてみます。

% sudo apt-get i2c-tools
I2Cアドレス

このモジュールでI2CをしゃべっているのはPICのコントローラなのですが
(この人があまりきちんとしていないためにいろいろが制限ある)、以下のアドレスにマップされています。

スレーブアドレス: 0x50
レジスタアドレス
 コマンド: 0x00
 データ: 0x80

コントロールコマンド

コマンド内容については、製品添付の仕様書は印刷が読みづらい上に必要な説明があんまりないので、
LCDコントローラの仕様書を見たほうがいいです。

以下はi2csetを使った設定例です。

% sudo /usr/sbin/i2cset -y 1 0x50 0x00 0x01     # clear display
% sudo /usr/sbin/i2cset -y 1 0x50 0x00 0x38     # function set: 8bit, 2lines, 5x8
% sudo /usr/sbin/i2cset -y 1 0x50 0x00 0x0c     # display on, no cursor
% sudo /usr/sbin/i2cset -y 1 0x50 0x00 0x06     # direction=INC, no shift
データ書き込み

同じくi2csetを使ってデータを書き込みます。

% sudo /usr/sbin/i2cset -y 1 0x50 0x80 0x41     # 'A'
% sudo /usr/sbin/i2cset -y 1 0x50 0x80 0x42     # 'B'
% sudo /usr/sbin/i2cset -y 1 0x50 0x80 0x43     # 'C'
% sudo /usr/sbin/i2cset -y 1 0x50 0x00 0xc0     # move to second line
% sudo /usr/sbin/i2cset -y 1 0x50 0x80 0x61     # 'a'
% sudo /usr/sbin/i2cset -y 1 0x50 0x80 0x62     # 'b'
% sudo /usr/sbin/i2cset -y 1 0x50 0x80 0x63     # 'c'

以上で基本的な動作が確認できました。

サンプルアプリ

簡単にCでコード書いてみました。コマンドのビット定義はこんなに気合入れるつもり無かったんですが...
busyloop() でタイミング調整してるところは時間は適当なのであしからず。

/*
 *            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 <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>

#define RPI_I2C_DEV             "/dev/i2c-1"    /* Rev.2 */
#define ACM1602N1_SLAVE         0x50
#define ACM1602N1_ADR_CMD       0x0
#define ACM1602N1_ADR_DAT       0x80

#define ACM1602N1_CMD_CLEAR_DISP                0x01
#define ACM1602N1_CMD_RETURN_HOME               0x02

#define ACM1602N1_CMD_ENTRY_MODE_ID_INC         0x02
#define ACM1602N1_CMD_ENTRY_MODE_ID_DEC         0x00
#define ACM1602N1_CMD_ENTRY_MODE_S_SHIFT        0x01
#define ACM1602N1_CMD_ENTRY_MODE_S_NOSHIFT      0x00

#define ACM1602N1_CMD_ENTRY_MODE(id, s) \
        (0x04 | \
         ACM1602N1_CMD_ENTRY_MODE_ID_##id | \
         ACM1602N1_CMD_ENTRY_MODE_S_##s)

#define ACM1602N1_CMD_DISP_ON_OFF_DISP_ON       0x04
#define ACM1602N1_CMD_DISP_ON_OFF_DISP_OFF      0x00
#define ACM1602N1_CMD_DISP_ON_OFF_CUR_ON        0x02
#define ACM1602N1_CMD_DISP_ON_OFF_CUR_OFF       0x00
#define ACM1602N1_CMD_DISP_ON_OFF_BLINK_ON      0x01
#define ACM1602N1_CMD_DISP_ON_OFF_BLINK_OFF     0x00

#define ACM1602N1_CMD_DISP_ON_OFF(disp, cursor, blink) \
        (0x08 | \
         ACM1602N1_CMD_DISP_ON_OFF_DISP_##disp | \
         ACM1602N1_CMD_DISP_ON_OFF_CUR_##cursor | \
         ACM1602N1_CMD_DISP_ON_OFF_BLINK_##blink)

#define ACM1602N1_CMD_CUR_DISP_SHIFT_SC_DISP    0x08
#define ACM1602N1_CMD_CUR_DISP_SHIFT_SC_CUR     0x00
#define ACM1602N1_CMD_CUR_DISP_SHIFT_DIR_RIGHT  0x04
#define ACM1602N1_CMD_CUR_DISP_SHIFT_DIR_LEFT   0x00

#define ACM1602N1_CMD_CUR_DISP_SHIFT(sc, dir) \
        (0x10 | \
         ACM1602N1_CMD_CUR_DISP_SHIFT_SC_##sc | \
         ACM1602N1_CMD_CUR_DISP_SHIFT_DIR_##dir)

#define ACM1602N1_CMD_FUNCTION_DATA_LEN_8BIT            0x10
#define ACM1602N1_CMD_FUNCTION_DATA_LEN_4BIT            0x00
#define ACM1602N1_CMD_FUNCTION_LINE_NUMBER_2LINES       0x08
#define ACM1602N1_CMD_FUNCTION_LINE_NUMBER_1LINE        0x00
#define ACM1602N1_CMD_FUNCTION_FONT_5x11                0x04
#define ACM1602N1_CMD_FUNCTION_FONT_5x8                 0x00

#define ACM1602N1_CMD_FUNCTION_SET(data_len, line_number, font) \
        (0x20 | \
         ACM1602N1_CMD_FUNCTION_DATA_LEN_##data_len | \
         ACM1602N1_CMD_FUNCTION_LINE_NUMBER_##line_number | \
         ACM1602N1_CMD_FUNCTION_FONT_##font)

#define ACM1602N1_CMD_SET_CGRAM_ADR(adr)        (0x40 | adr)
#define ACM1602N1_CMD_SET_DDRAM_ADR(adr)        (0x80 | adr)

static void busyloop(unsigned long n)
{
        volatile unsigned long i;       /* volatile not to be optimized */
        for (i = 0; i < n; i++);
}

static int acm_cmd_write(int fd, unsigned char cmd)
{
        const unsigned char buf[2] = {ACM1602N1_ADR_CMD, cmd};

        if (write(fd, buf, 2) != 2) {
                fprintf(stderr, "ACM1602N1 command write error: 0x%02x\n", cmd);
                return -1;
        }

        return 0;
}

static int acm_dat_write(int fd, const unsigned char *dat, int n)
{
        unsigned char buf[n * 2];
        int i;

        for (i = 0; i < n; i++) {
                buf[i * 2] = ACM1602N1_ADR_DAT;
                buf[i * 2 + 1] = dat[i];
        }
        if (write(fd, buf, n * 2) != n * 2) {
                fprintf(stderr,
                        "ACM1602N1 data write error: data=[0x%02x, ...]\n",
                        dat[0]);
                return -1;
        }

        return 0;
}

static int init_acm(int fd)
{
        if (acm_cmd_write(fd, ACM1602N1_CMD_CLEAR_DISP) < 0)
                return -1;
        busyloop(0x10000);      /* clear display takes some millisecs */
        if (acm_cmd_write(fd,
                          ACM1602N1_CMD_FUNCTION_SET(8BIT, 2LINES, 5x8)) < 0)
                return -1;
        if (acm_cmd_write(fd, ACM1602N1_CMD_DISP_ON_OFF(ON, OFF, OFF)) < 0)
                return -1;
        if (acm_cmd_write(fd, ACM1602N1_CMD_ENTRY_MODE(INC, NOSHIFT)) < 0)
                return -1;

        return 0;
}

int main(void)
{
        int fd;
        const unsigned char test_dat1[] = "good morning!";
        const unsigned char test_dat2[] = {
                0xb5, 0xca, 0xd6, 0xb3, 0xba, 0xde, 0xbb, 0xde, 0xb2, 0xcf, 0xbd, 0x00
        };

        if ((fd = open(RPI_I2C_DEV, O_RDWR)) < 0) {
                printf("Faild to open i2c port\n");
                return 1;
        }
        if (ioctl(fd, I2C_SLAVE, ACM1602N1_SLAVE) < 0) {
                printf("Unable to get bus access to talk to slave\n");
                return 1;
        }

        if (init_acm(fd) < 0)
                return 1;
        if (acm_dat_write(fd, test_dat1, sizeof(test_dat1) - 1) < 0)
                return 1;
        if (acm_cmd_write(fd, ACM1602N1_CMD_SET_DDRAM_ADR(0x40)) < 0)
                return 1;
        if (acm_dat_write(fd, test_dat2, sizeof(test_dat2) - 1) < 0)
                return 1;

        return 0;
}

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() でしか発行できないので
こちらを使わざるを得ないことも多いような気もするのですが...

Raspberry Pi でI2C: ハブ作成

I2Cデバイスを複数つなげるために、ハブ作成しました。
USBハブっぽい感じで。
他のボードにも使い回せそうですしね。

VDD/SCL/SDA/GND の4端子なんですが、割り込み用にGPIO引いといた方がよかったかも...