I2C on FreeBSD/Raspberry Pi
https://gyazo.com/5851311ec9f75378cd91eb923750bf32
はじめに
ここでは、Raspberry PiのI2Cを、FreeBSDで操作する方法について説明します。
参考文献
I2Cってなあに?
I2Cは、以下の様な特徴を持つ、2線式のバスです。
Inter-Integrated Circuit (I2C; アイ・スクエアド・シー、アイ・アイ・シー、アイツーシー) (Wikipedia) 比較的低速のシリアルバス
シリアルデータ (SDA) とシリアルクロック (SCL) の2線で通信
制御を行うPC側はマスタ、デバイス側はスレーブと呼ぶ
個々のスレーブは、自分を指定するためのアドレスを持つ
バスには、アドレスで区別される複数のスレーブを接続することが可能
組み込みシステムなどで良く使われる
Raspberry Piなどの小型ボードコンピュータでも使えることが多い
FreeBSDのI2Cデバイス
Raspberry Piでは、以下のようにiic0とiic1が見つかります。
/dev/iic1がRaspberry Piの40ピン端子に出ている方です。
code:dmesg
% dmesg | grep iic
iichb0: <BCM2708/2835 BSC controller> mem 0x205000-0x20501f irq 10 on simplebus0
iichb1: <BCM2708/2835 BSC controller> mem 0x804000-0x80401f irq 11 on simplebus0
iicbus0: <OFW I2C bus> on iichb0
iic0: <I2C generic I/O> on iicbus0
iicbus1: <OFW I2C bus> on iichb1
iic1: <I2C generic I/O> on iicbus1
FreeBSDでユーザランドでI2Cの操作をする
FreeBSDには、i2cコマンドがあります(man 8 i2c)。
ただし、FreeBSDで提供されているi2cコマンドは、Raspberry Piではうまく動かないという問題があります。
Raspberry Pi の i2c ドライバでは実装されてない機能をi2cでは使っている
(2019/12/24追記)-m trオプションが追加され、i2cコマンドでもpi2cコマンド相当のことができそうです
例: I2Cバスに繋がっているデバイスのアドレス(23,3c,3f)を確認する
code:shell
% sudo /home/mutoh/newi2c/pi2c -s -f /dev/iic1
Scanning I2C devices on /dev/iic1: 23 3c 3f
FreeBSDでI2Cを使ってみる
FreeBSDでI2Cを使うには、以下のような方法があります。
1. ユーザランドコマンドi2c(pi2c)を使う
2. Cでioctlを使ったプログラムを書く
FreeBSD I2C用のC言語API
FreeBSDでI2CをC言語から利用するためには、man iicから調べると良いでしょう。
I2Cの操作には、man 4 iicで定義されているioctlを使用します。
man 4 iic
device iic
#include <dev/iicbus/iic.h>
I2Cデバイスを利用するときには、はじめに、I2Cデバイス(/dev/iic1など)をopen()します。
読み書きにはI2CRDWRをフラグに与え、struct iic_rdwr_dataを引数に設定して、ioctl()を実行します。
読み書きのモード設定は、struct iic_msg.flagsに、読み込みの場合はIIC_M_RDを、書き込みの場合は!IIC_M_RD(または、IIC_M_WR)を与えます。
table:C言語API
FreeBSDでのC言語API 備考
初期化(init) fd=open("/dev/iic1", O_RDWR) 以下のioctlでは、ここで作られたfdを使う
write ioctl(fd, I2CRDWR, &cmd(*1)) iic_msg.flags=!IIC_M_RD (or IIC_M_WR)
read ioctl(fd, I2CRDWR, &cmd) iic_msg.flags=IIC_M_RD
(*1)cmdはstruct iic_rdwr_data構造体
操作用構造体は3つ定義されていますが、むとうは後ろの2つしか使ったことがありません。
操作用構造体
struct iiccmd
code:iiccmd.c
struct iiccmd {
u_char slave; /* I2Cスレーブのアドレス */
int count;
int last;
char *buf;
};
struct iic_msg スレーブアドレスやデータを含む構造体
code:iic_msg.c
struct iic_msg
{
uint16_t slave; /* I2Cスレーブのアドレス */
uint16_t flags; /* 以下で定義されるフラグ */
#define IIC_M_WR 0 /* 書き込み用の偽フラグ */ #define IIC_M_RD 0x0001 /* 書き込みか読み込みか */ #define IIC_M_NOSTOP 0x0002 /* メッセージの後で、I2C stopを送らない */ #define IIC_M_NOSTART 0x0004 /* メッセージの前に、I2C startを送らない */ uint16_t len; /* メッセージの長さ */
uint8_t * buf; /* メッセージ本体 */
};
struct iic_rdwr_data 送受信するデータを表現する構造体
code:iic_rdwr_data.c
struct iic_rdwr_data {
struct iic_msg *msgs; /* struct iic_msg配列へのポインタ */
uint32_t nmsgs; /* 送受信するメッセージの数 */
};
Button SHIM
では、色々なデバイスでI2Cを使ってみましょう。
https://gyazo.com/04750550d3f329b785aa091ddf30102c
Button SHIMは、Raspberry Piの40pin端子に接続できる、5ボタンスイッチ(TCA9554A)+1フルカラーLED(APA102)のHat(シールド)です。
ボタンやLEDはI2C接続です。
Button SHIM
データシート
Button SHIMをシェルスクリプトで使う
https://www.youtube.com/watch?v=IA7oHvq0b_o
まずはじめに、シェルスクリプトでButton SHIMを使ってみます。
ここでは、ボタンスイッチの状態を16進数で表示しています。
code:buttonshim.sh
I2C=/home/mutoh/newi2c/pi2c
DEV=/dev/iic1
ADDR=0x3f
outbin() {
printf '\'echo "ibase=16;obase=8 ;"$1 | bc
}
outbin 1F | sudo ${I2C} -f ${DEV} -a ${ADDR} -d w -c 1 -v -b -o 0x3
outbin 00 | sudo ${I2C} -f ${DEV} -a ${ADDR} -d w -c 1 -v -b -o 0x2
outbin 00 | sudo ${I2C} -f ${DEV} -a ${ADDR} -d w -c 1 -v -b -o 0x1
while true
do
sudo ${I2C} -f ${DEV} -a ${ADDR} -d r -c 1
sleep 1
done
初期化には、0x1F,0x00,0x00を送る必要があります。
readする(-d r)と、1バイト毎(-c 1)に、ボタンスイッチの状態を読み込むことができます。
Button SHIM をpi2cを使って実装したPythonライブラリで使う
次に、Button SHIM用のpythonライブラリを修正して、Button SHIMを使ってみます。
I2Cの操作部分は、pi2cコマンドを呼び出すことで実現しています。
今回は、LEDの点灯も行える様になっています。
この様にライブラリで実装することで、button-shim/examples/以下のデモをそのまま動かせることになります。
Pythonライブラリの中から、pi2cを呼び出すように変更するだけ
setup(),write_block_data(),_write_data(),_read_data()を変更
code:button-shim/library/buttonshim/__init__.py.diff
--- __init__.py.org 2018-06-27 20:54:18.719671000 +0000
+++ __init__.py 2018-06-28 02:18:21.640175000 +0000
@@ -1,8 +1,11 @@
-import smbus
+import subprocess
+import sys
import time
from threading import Thread
import atexit
from colorsys import hsv_to_rgb
+i2c_cmd="sudo /home/mutoh/newi2c/pi2c -f /dev/iic1 -a 0x3f -d w -b "
+i2c_read="sudo /home/mutoh/newi2c/pi2c -f /dev/iic1 -a 0x3f -d r -c 1"
try:
import queue
@@ -121,9 +124,9 @@
try:
if led_data:
for chunk in _chunk(led_data, 32):
- _bus.write_i2c_block_data(ADDR, REG_OUTPUT, chunk)
+ write_block_data(ADDR,REG_OUTPUT,chunk)
- _states = _bus.read_byte_data(ADDR, REG_INPUT)
+ _states = _read_data(ADDR, REG_INPUT)
except IOError as e:
_errors += 1
@@ -177,17 +180,35 @@
_running = False
_t_poll.join()
+def write_block_data(addr,command,chunk):
+ data=""
+ c=0
+ for d in chunk:
+ data+="\\"+str(format(d,'o'))
+ c+=1
+ if(c>0):
+ cmd="printf \"" + data + "\" | " + i2c_cmd + "-c "+str(c)+" -o 0x" + str(command)
+ subprocess.check_output(cmd,shell=True)
+
+def _write_data(addr, command, value):
+ cmd="printf \"\\" + str(oct(value)) + "\" | " + i2c_cmd + "-c 1 -o 0x" + str(command)
+ subprocess.check_output(cmd,shell=True)
+
+def _read_data(addr, cmd):
+ cmd=i2c_read
+ d=subprocess.check_output(cmd.split(),stderr=subprocess.STDOUT)
+ return(int(d,16))
+
def setup():
global _t_poll, _bus
if _bus is not None:
return
- _bus = smbus.SMBus(1)
-
- _bus.write_byte_data(ADDR, REG_CONFIG, 0b00011111)
- _bus.write_byte_data(ADDR, REG_POLARITY, 0b00000000)
- _bus.write_byte_data(ADDR, REG_OUTPUT, 0b00000000)
+ _bus = 1
+ _write_data(ADDR, REG_CONFIG, 0b00011111)
+ _write_data(ADDR, REG_POLARITY, 0)
+ _write_data(ADDR, REG_OUTPUT, 0)
_t_poll = Thread(target=_run)
_t_poll.daemon = True
Button SHIMをC言語APIで使う
次に、シェルスクリプトで実装したボタン入力をC言語APIを使って実装してみます。
code:buttonshield.c
static int
i2c_write(uint8_t data,int offset)
{
int fd,i;
int bufsize=1;
struct iic_msg msg;
struct iic_rdwr_data rdwr;
uint8_t *buf,*dbuf;
fd = open(I2C_DEV, O_RDWR);
dbuf = malloc( 2 * sizeof(uint8_t) );
msg.slave = I2C_ADDR << 1 | !IIC_M_RD;
msg.flags = !IIC_M_RD;
msg.len = 2 * sizeof( uint8_t );
msg.buf = dbuf;
rdwr.msgs = &msg;
rdwr.nmsgs = 1;
ioctl(fd, I2CRDWR, &rdwr);
close(fd);
return (0);
}
static int
i2c_read()
{
int fd;
struct iic_msg *msg;
struct iic_rdwr_data rdwr;
uint8_t *i2c_buf;
fd = open(I2C_DEV, O_RDWR);
i2c_buf = malloc(2);
msg = malloc( 2 * sizeof(struct iic_msg) );
msg0.slave = I2C_ADDR << 1 | !IIC_M_RD; msg0.len = sizeof( uint8_t ); msg1.slave = I2C_ADDR << 1 | IIC_M_RD; msg1.len = sizeof( uint8_t ); rdwr.msgs = msg;
rdwr.nmsgs = 2;
ioctl(fd, I2CRDWR, &rdwr);
printf("Data:%02x \n",i2c_buf0); close(fd);
return (0);
}
int
main(int argc, char** argv)
{
/* Initialize */
i2c_write(0x1f,0x03);
i2c_write(0x00,0x02);
i2c_write(0x00,0x01);
/* Read forever */
while (1)
{
i2c_read();
sleep(1);
}
}
WeMos mini Shiled
https://gyazo.com/cb6d3286840d1e1ccb68720e190152f8
ここからは、ESP8266互換のWeMos用に提供されているI2Cモジュールを使って、同じ様にデモを行います。 WeMos用シールドはコンパクトで、GPIO接続のものからI2C接続のものまで色々用意されており、さらに価格も安く、見ているだけで楽しくなってきます。 ここでは、BH1750を使った環境光シールドと、SSD1306を使ったOLEDシールドについて、デモを行います。
BH1750 with Shell script and C
https://www.youtube.com/watch?v=HQNcUglwgB4
明るいほど、大きな値を返すようになっています。
I2Cスレーブアドレス: 0x23
初期化には、0x10を送ります。
環境光センサーの値は、2バイトで送られてくるため、読み込みを2バイト(-d r -c 2)で行います。
Shellの場合
code:bh1750.sh
I2C=/home/mutoh/newi2c/pi2c
DEV=/dev/iic1
ADDR=0x23
outbin() {
printf '\'echo "ibase=16;obase=8 ;"$1 | bc
}
outbin 10 | sudo ${I2C} -f ${DEV} -a ${ADDR} -d w -v -b
sleep 0.1
while true
do
sudo ${I2C} -f ${DEV} -a ${ADDR} -d r -c 2 -v
sleep 1
done
Cの場合も、同じ様に作成することができます。
ソースは、省略しますので、リンク先をご覧ください。
Cの場合
SSD1306をC言語APIで使う
https://www.youtube.com/watch?v=snlhmHP_r80
SSD1306は、I2C(もしくはSPI)を使って制御できるOLEDディスプレイです。
今のモデルには、I2C接続の2つのボタン(スレーブアドレス0x31)があるのですが、古いモデルにはありません。
I2Cスレーブアドレス: 0x3c
ソースは一見複雑に見えますが、所定の初期化コマンドを送った後に、表示するデータ(buffer[])を送っているだけです。
おわりに
Raspberry PiのFreeBSDで、I2Cを使ってみました。
I2Cには色々なデバイスがあるので、仕様書を読みながらチョコチョコと作業をして、動いた時はとてもうれしいです。
みなさんも、一緒に遊んでみませんか?
編集履歴
2020/05/27: presentation modeが使えるように調整
2019/12/24: -m trオプションの追加に関して記述
2019/12/23: 最初の公開版