起動時にブラウザをフルスクリーンモードで表示する
今回やってみたことを、まずは結果からお見せします。左が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
- メディア: エレクトロニクス
- この商品を含むブログ (2件) を見る
タクトスイッチ以外はアセンブル済の商品で、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 の配線を整理
だんだんデバイスが増えてきて配線がとっちらかってきたので、100均のメッシュバスケットとタイラップで整理してみました。
結構すっきりしました(^_^)
Raspberry Pi で赤外線リモコン
ようやく、というかずっとやる予定でやっていなかった Raspberry Piを赤外線リモコンにする というのをやってみました。
参照:http://homebrew.jp/show?page=1480
ハードウェア
パーツ
パーツは "Arduinoでリモコン" で使ったのと同じ物を使います。
- LED:OSI5FU5111C-40 (1.35V, 最大100mA)
- 受信モジュール: PL-IRM-2161-C438
秋月で購入しました。
この他に抵抗、トランジスタが必要です。
接続
以下の図のような感じで回路を組みました。
電源については注意が必要で、 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(Ω) なので、これに近い値のものを使います。
配線は少し整理してこんな感じにしてあります。
ソフトウェア
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 toso 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のリード・ライトのみに限定しています。
ここでインクルードしている
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, ®, 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引いといた方がよかったかも...