Raspberry Pi 日記 (part2)

長嶋 洋一


2013年6月19日(水)

水曜日、講義のない日である。 3限に及川さんのアポ(たぶん映像作品の音楽の相談)、その後に外出の予定があり、放課後にはアカペラなので、午前中が稼ぎ時である。 昨日、モバイルWiFiルータを使ってapt-getして、その1-dayの利用時間がまだあるので、ちょっと先走って、 Blink(1) の関係にトライしてみる事にした。 たぶんうまく行かないので、ここでまた、質問メイルをふーみんに送る、という計画である(^_^;)。

まず、Blink(1)の公式サイトは ここ である。 この右下あたりに「BLINK(1) TOOL : OSX - WINDOWS - LINUX_x86 - LINUX_x64 - RASPBERRY PI - GURUPLUG」というリンクがあり、Raspberry Pi版のBlink(1)ツールがあるのでダウンロードすると これ であり、解凍してRaspberry Piに送っても、実行させると以下のように「no blink(1) devices found」と表示されて、デバイスが認識されない。 おそらくこれは、defaultではRaspberry PiにUSB HIDの関係が全てはインストールされていないのだろう。

pi@raspberrypi ~/Desktop $ ./blink1-tool
Usage: 
  blink1-tool <cmd> [options]
where <cmd> is one of:
  --hidread                   Read a blink(1) USB HID GetFeature report 
  --hidwrite <listofbytes>    Write a blink(1) USB HID SetFeature report 
  --eeread <addr>             Read an EEPROM byte from blink(1)
  --eewrite <addr>,<val>      Write an EEPROM byte to blink(1) 
  --blink <numtimes>          Blink on/off 
  --random <numtimes>         Flash a number of random colors 
  --rgb <red>,<green>,<blue>  Fade to RGB value
  --savergb <r>,<g>,<b>,<pos> Write pattern RGB value at pos
  --readrgb <pos>             Read pattern RGB value at pos
  --servertickle <on/off>     Turn on/off servertickle (uses -t msec) 
  --on                        Turn blink(1) full-on white 
  --off                       Turn blink(1) off 
  --list                      List connected blink(1) devices 
  --version                   Display blink(1) firmware version 
and [options] are: 
  -g -nogamma                 Disable autogamma correction
  -d dNums --id all|deviceIds Use these blink(1) ids (from --list) 
//--serial <num>              Connect to blink(1) by its serial number 
  -m ms,   --miilis=millis    Set millisecs for color fading (default 300)
  -t ms,   --delay=millis     Set millisecs between events (default 500)
  --vid=vid --pid=pid         Specifcy alternate USB VID & PID
  -v, --verbose               verbose debugging msgs

Examples 
  blink1-tool -m 100 --rgb 255,0,255   # fade to #FF00FF in 0.1 seconds 
  blink1-tool -t 2000 --random 100     # every 2 seconds new random color

pi@raspberrypi ~/Desktop $ ./blink1-tool -m 100 --rgb 255,0,255
no blink(1) devices found

pi@raspberrypi ~/Desktop $
そして、たぶん これ をゲットした頃にどこかでゲットしてきたらしい(^_^;)、「blink1hid-demo.py」と「blink1-ctypes.py」の2本がずっとMacのデスクトップに置かれていたのだが、「blink1hid-demo.py」の内容は以下である。
#!/usr/bin/python
"""
blink1hid-demo.py -- Low-level HID access with Python
Thanks to Aaron Blondeau for this

As he says:
"I am working on controlling a Blink1 from an Android.
In order to understand how the set feature calls work without wearing out
the usb port on my Android I have created a python script that uses the pyusb library.
The code is as follows if you wish to share and save some time for other folks."

"""

import usb # https://github.com/walac/pyusb
          # + "brew install libusb" on osx
          # + libusb-win32 (inf method) on windows

#Find the Blink1
dev = usb.core.find(idVendor=0x27b8, idProduct=0x01ed)
assert dev is not None

#The Blink1 takes 8 bytes of input
# 1=report_id (0)
# 2=action (c = fade to rgb, n = set rgb now)
# 3=red
# 4=green
# 5=blue
# 6=th : time/cs high (T >>8) where time 'T' is a number of 10msec ticks
# 7=tl : time/cs low (T & 0xff)
# 8=step (0)

# once a buffer is set with these bytes, we need to do what blink1_write / hid_send_feature_report does
# https://github.com/todbot/blink1/blob/master/commandline/blink1-lib.c
# https://github.com/signal11/hidapi/blob/master/libusb/hid.c

#set color to red
bmRequestTypeOut = usb.util.build_request_type(usb.util.CTRL_OUT, usb.util.CTRL_TYPE_CLASS, usb.util.CTRL_RECIPIENT_INTERFACE)
action = 0x6E # ='n' (set rgb now)
red = 0xFF
green = 0x00
blue = 0x00
dev.ctrl_transfer(bmRequestTypeOut, 0x09, (3 << 8) | 0x01, 0, [0x00, action, red, green, blue, 0x00, 0x00, 0x00, 0x00])

#then fade to blue
action = 0x63 # ='c' (fade to rgb)
red = 0x00
green = 0x00
blue = 0xFF
T = 5000/10 #5 seconds worth of 10msec tics
th = (T & 0xff00) >> 8
tl = T & 0x00ff
dev.ctrl_transfer(bmRequestTypeOut, 0x09, (3 << 8) | 0x01, 0, [0x00, action, red, green, blue, th, tl, 0x00, 0x00])

#get version number
import time
import string
action = 0x76 # ='v' (version)
dev.ctrl_transfer(bmRequestTypeOut, 0x09, (3 << 8) | 0x01, 0, [0x00, action, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
time.sleep(.05)
bmRequestTypeIn = usb.util.build_request_type(usb.util.CTRL_IN, usb.util.CTRL_TYPE_CLASS, usb.util.CTRL_RECIPIENT_INTERFACE)
version_raw = dev.ctrl_transfer(bmRequestTypeIn, 0x01, (3 << 8) | 0x01, 0, 8)
version = ''.join(chr(i) for i in version_raw) # items in the array should correspond to ascii codes for something like "v 100"
version = filter(lambda x: x in string.printable, version)
print version #the c code must tack on an extra 0?
この冒頭に書かれている pyusb というのも気になる、 おそらく「PythonでUSB」というライブラリだろう。 そして「blink1-ctypes.py」の内容は以下である。
#/usr/bin/python
"""
blink1.py -- Python interface to libblink1 through the magic of ctypes

import blink1
dev = blink1.open()
blink1.setRGB(d, 255,0,0) # Red
...

Do not:
from blink1 import *
or you will polute your namespace with some very common names (i.e. File I/O)

Make sure the "blink1-lib" shared library / DLL is in your path
for more info see:
https://getsatisfaction.com/thingm/topics/more_comprehensive_python_support

Thanks to Stephen Youndt for this
"""

from ctypes import *
from ctypes.util import find_library
import inspect, os
import glob
# Find the library
localpath = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
libname = find_library("blink1")
if libname is None:
    libname = find_library("Blink1")
if libname is None:
    libname = find_library("blink1-lib")
if libname is None:
    libname = glob.glob(os.path.join(localpath, '[Bb]link1-lib.so'))[-1]
if libname is None:
    libname = glob.glob(os.path.join(localpath, 'blink1-lib.dll'))[-1]
if libname is None:
    libname = glob.glob(os.path.join(localpath, 'lib[Bb]link1*'))[-1]
# If we found the library, load it
assert libname is not None
libblink1 = CDLL(libname)
vid = libblink1.blink1_vid
vid.restype = c_int
pid = libblink1.blink1_pid
pid.restype = c_int
sortPaths = libblink1.blink1_sortPaths
sortSerials = libblink1.blink1_sortSerials
enumerate = libblink1.blink1_enumerate
enumerate.restype = c_int
enumerateByVidPid = libblink1.blink1_enumerateByVidPid
enumerateByVidPid.restype = c_int
enumerateByVidPid.argtypes = [c_int, c_int]
getCachedPath = libblink1.blink1_getCachedPath
getCachedPath.restype = c_char_p
getCachedPath.argtypes = [c_int]
getCachedSerial = libblink1.blink1_getCachedSerial
getCachedSerial.restype = c_wchar_p
getCachedSerial.argtypes = [c_int]
getCachedCount = libblink1.blink1_getCachedCount
getCachedCount.restype = c_int
open = libblink1.blink1_open
open.restype = c_void_p
openByPath = libblink1.blink1_openByPath
openByPath.restype = c_void_p
openByPath.argtypes = [c_char_p]
openBySerial = libblink1.blink1_openBySerial
openBySerial.restype = c_void_p
openBySerial.argtypes = [c_wchar_p]
openById = libblink1.blink1_openById
openById.restype = c_void_p
openById.argtypes = [c_int]
close = libblink1.blink1_close
close.argtypes = [c_void_p]
write = libblink1.blink1_write
write.restype = c_int
write.argtypes = [c_void_p, c_void_p, c_int]
read = libblink1.blink1_read
read.restype = c_int
read.argtypes = [c_void_p, c_void_p, c_int]
getSerialNumber = libblink1.blink1_getSerialNumber
getSerialNumber.restype = c_int
getSerialNumber.argtypes = [c_void_p, c_char_p]
getVersion = libblink1.blink1_getVersion
getVersion.restype = c_int
getVersion.argtypes = [c_void_p]
fadeToRGB = libblink1.blink1_fadeToRGB
fadeToRGB.restype = c_int
fadeToRGB.argtypes = [c_void_p, c_ushort, c_ubyte, c_ubyte, c_ubyte]
setRGB = libblink1.blink1_setRGB
setRGB.restype = c_int
setRGB.argtypes = [c_void_p, c_ubyte, c_ubyte, c_ubyte]
eeread = libblink1.blink1_eeread
eeread.restype = c_int
eeread.argtypes = [c_void_p, c_ushort, c_void_p]
eewrite = libblink1.blink1_eewrite
eewrite.restype = c_int
eewrite.argtypes = [c_void_p, c_ushort, c_void_p]
serialnumread = libblink1.blink1_serialnumread
serialnumread.restype = c_int
serialnumread.argtypes = [c_void_p, c_void_p]
serialnumwrite = libblink1.blink1_serialnumwrite
serialnumwrite.restype = c_int
serialnumwrite.argtypes = [c_void_p, c_void_p]
serverdown = libblink1.blink1_serverdown
serverdown.restype = c_int
serverdown.argtypes = [c_void_p, c_ubyte, c_ushort]
play = libblink1.blink1_play
play.restype = c_int
play.argtypes = [c_void_p, c_ubyte, c_ubyte]
writePatternLine = libblink1.blink1_writePatternLine
writePatternLine.restype = c_int
writePatternLine.argtypes = [c_void_p, c_ushort, c_ubyte, c_ubyte, c_ubyte, c_ubyte]
readPatternLine = libblink1.blink1_readPatternLine
readPatternLine.restype = c_int
readPatternLine.argtypes = [c_void_p, c_void_p, c_void_p, c_void_p, c_void_p, c_void_p]
error_msg = libblink1.blink1_error_msg
error_msg.restype = c_char_p
error_msg.argtypes = [c_int]
enableDegamma = libblink1.blink1_enableDegamma
disableDegamma = libblink1.blink1_disableDegamma
degamma = libblink1.blink1_degamma
degamma.restype = c_int
degamma.argtypes = [c_int]
sleep = libblink1.blink1_sleep
sleep.argtypes = [c_ushort]
この2本のPythonブログラムをRaspberry Piに送って実行すると、以下のようなエラーが出た。 これはまぁ、詳細不明である。(^_^;)

pi@raspberrypi ~ $ ls
Desktop         blink1-ctypes.py  blink1-tool-raspi.zip  ocr_pi.png    pyusb-1.0.0a3      test.py
RPi.GPIO-0.1.0  blink1-tool       blink1hid-demo.py      python_games  pyusb-1.0.0a3.zip  twilight.png

pi@raspberrypi ~ $ sudo python blink1hid-demo.py
Traceback (most recent call last):
  File "blink1hid-demo.py", line 42, in 
    dev.ctrl_transfer(bmRequestTypeOut, 0x09, (3 << 8) | 0x01, 0, [0x00, action, red, green, blue, 0x00, 0x00, 0x00, 0x00])
  File "/usr/local/lib/python2.7/dist-packages/usb/core.py", line 702, in ctrl_transfer
    self.__get_timeout(timeout)
  File "/usr/local/lib/python2.7/dist-packages/usb/backend/libusb1.py", line 596, in ctrl_transfer
    timeout))
  File "/usr/local/lib/python2.7/dist-packages/usb/backend/libusb1.py", line 403, in _check
    raise USBError(_str_error[ret], ret, _libusb_errno[ret])
usb.core.USBError: [Errno 5] Input/output error

pi@raspberrypi ~ $ sudo python blink1-ctypes.py
Traceback (most recent call last):
  File "blink1-ctypes.py", line 33, in 
    libname = glob.glob(os.path.join(localpath, '[Bb]link1-lib.so'))[-1]
IndexError: list index out of range

pi@raspberrypi ~ $ 
また、関連して検索すると このような ところも出て来た。 これも関係するかもしれない。 そして、 このページ には、「Installing PyUSB on GNU/Linux Systems」という、以下の説明があった。
$ sudo apt-get install python libusb

$ sudo python setup.py install
これは見慣れたやつである(^_^)。 そこで、またまたRaspberry Pi63号機を固定IPからDHCPにして、「sudo apt-get install python libusb」をしてみたが、「libusb」というライブラリは無いぞ、と叱られた(^_^;)。 ただし、 このページ に行って、「PyUSB is available for download in the SourceForge project page. 」とあったので このページ に行き、 これ をゲットできた。 この内容は以下のような構成であり、チュートリアルは これ である。 なんか、Pythonプログラムのオンパレードである。

ここにある「readme」に従って、「setup.python」を実行すると、以下のようにエラーが出なかったが、やはりそれでも「blink1hid-demo.py」を走らせようとするとエラーが出た。(^_^;)

チュートリアル を読む感じでは、やはり、ここはC言語でRaspberry Piを叩くという方向に行く必要がありそうである。 ここでフト、WiFiのサイトに行って、ここまでの利用の記録を見てみた。 やはり、パケット数は相当なもののようで、とうてい「従量制」では使い物にならない模様である。(^_^;)

さて、ふーみん本のGPIOの解説を読み進めていくと、いきなり、昨日のページに書いていた、Raspberry PiのGPIOポートのピンについて、新しい情報が続々と出て来た。 そういえば空きピンについて何も知らなかったが、ちゃんと電源とかGNDとか、あったのである。 そして、ふーみん本の情報から改訂した、Raspberry PiのGPIOポートのピン配置図が以下である。 今後はこちらを使っていこう。(^_^)

(この図は後で改訂されている事に注意)

ここから、ハンダごてとかブレッドボードとか電源とかLEDとかの話があって、一気に14ページほどすっとばした(^_^;)。 そしていよいよ、昨日LEDを付けたRaspberry Piに対して、まずはコマンドラインから叩く、という話となった。 GPIOはroot権限でないと叩けないので、ここではまず以下のように、「sudo」でなく、「sudo -i」として、rootになる事から始まる。

そして「echo "17" > ・・・」というところでハテと困った。 上のGPIOポートの17ピンは+3.3Vなのである(^_^;)。 そこでCQ本と照合した結果、なんとRaspberry PiのGPIOポートのピンと、ソフト側から叩く場合のGPIOポートの番号とは別物である、という驚愕の事実が発覚した。 つまりは、関係するところだけ挙げれば、以下のような対応なのだという。

ピン番号     信号名     ソフト上の呼び名
11GPIO 0GPIO17
12GPIO 1GPIO18
13GPIO 2GPIO21
15GPIO 3GPIO22
16GPIO 4GPIO23
18GPIO 5GPIO24
22GPIO 6GPIO25
7GPIO 7GPIO4
8TxGPIO14
10RxGPIO15
(この表は後で改訂されている事に注意)

これは、もの凄く、嫌である(^_^;)。 しかし仕方ないのでこれを何度も参照しよう。 すると、ここでCQ本で新たな発見があった。 なんと、サンプルとして、I2CとかSPIのピンを、スイッチ入力とかLED出力に使っているではないか。 そこで以下のように実験して、使わずに捨てるつもりだった計7ビットについても、ちゃんと出力ポートとして拡張できる事を確認した。 これは、「GPIO 0」から「GPIO 7」までの8ビットをデータバスとして使い、ラッチするためのアドレスやライトパルスに使えるので、たった8ビットという制約とは雲泥の違いとなる。 Pythonではアクセス出来ないとすれば使わないだけであり、これは相当に有益な発見なのだ(^_^)。 そこで、I2CとSPIの計7ビットについても追加して、「EXT 0」から「EXT 6」までのオリジナルの信号名を添えて、対応表を以下のように改訂した。

ピン番号     信号名     ソフト上の呼び名
11GPIO 0GPIO17
12GPIO 1GPIO18
13GPIO 2GPIO21
15GPIO 3GPIO22
16GPIO 4GPIO23
18GPIO 5GPIO24
22GPIO 6GPIO25
7GPIO 7GPIO4
8TxGPIO14
10RxGPIO15
3EXT 0GPIO0
5EXT 1GPIO1
19EXT 2GPIO10
21EXT 3GPIO9
23EXT 4GPIO11
24EXT 5GPIO8
26EXT 6GPIO7
(この表は後で改訂されている事に注意)

そしてrootになって、以下の手順でのポートの信号出力を確認した。 これは出力についてだが、とりあえずの拡張用途としては出力が多い(たくさんのプロセスが裏で動くUnixで入力のモニタをしたくない(^_^;))との想定である。 ちなみにCQ本の記述の中に、以下のどこかが抜けているバグも発見した。(^_^;)

そして、これらをいちいち手で打つのも嫌なので、Mac側のエディタで以下のような内容を作成して、テキストファイル化してrcpでRaspberry Piに送り、それをsudoでシェルスクリプトとして実行できる方法(「chmod -x shell.txt」とやると「shell.txt」がシェルスクリプトとして実行できる)をCQ本の記述から発見して、やってみた。 すると、SPIの5ビットは出力できたものの、I2Cの2ビットは沈黙を保ったままだった。 どうやら3ピンは内部プルアップしている事からも、入力専用のようである。

echo "0" > /sys/class/gpio/export
echo "1" > /sys/class/gpio/export
echo "10" > /sys/class/gpio/export
echo "9" > /sys/class/gpio/export
echo "11" > /sys/class/gpio/export
echo "8" > /sys/class/gpio/export
echo "7" > /sys/class/gpio/export
echo "out" > /sys/class/gpio/gpio0/direction
echo "out" > /sys/class/gpio/gpio1/direction
echo "out" > /sys/class/gpio/gpio10/direction
echo "out" > /sys/class/gpio/gpio9/direction
echo "out" > /sys/class/gpio/gpio11/direction
echo "out" > /sys/class/gpio/gpio8/direction
echo "out" > /sys/class/gpio/gpio7/direction
echo "1" > /sys/class/gpio/gpio0/value
echo "1" > /sys/class/gpio/gpio1/value
echo "1" > /sys/class/gpio/gpio10/value
echo "1" > /sys/class/gpio/gpio9/value
echo "1" > /sys/class/gpio/gpio11/value
echo "1" > /sys/class/gpio/gpio8/value
echo "1" > /sys/class/gpio/gpio7/value
sleep 1
echo "0" > /sys/class/gpio/gpio0/value
sleep 1
echo "1" > /sys/class/gpio/gpio0/value
echo "0" > /sys/class/gpio/gpio1/value
sleep 1
echo "1" > /sys/class/gpio/gpio1/value
echo "0" > /sys/class/gpio/gpio10/value
sleep 1
echo "1" > /sys/class/gpio/gpio10/value
echo "0" > /sys/class/gpio/gpio9/value
sleep 1
echo "1" > /sys/class/gpio/gpio9/value
echo "0" > /sys/class/gpio/gpio11/value
sleep 1
echo "1" > /sys/class/gpio/gpio11/value
echo "0" > /sys/class/gpio/gpio8/value
sleep 1
echo "1" > /sys/class/gpio/gpio8/value
echo "0" > /sys/class/gpio/gpio7/value
sleep 1
echo "1" > /sys/class/gpio/gpio7/value
echo "0" > /sys/class/gpio/unexport
echo "1" > /sys/class/gpio/unexport
echo "10" > /sys/class/gpio/unexport
echo "9" > /sys/class/gpio/unexport
echo "11" > /sys/class/gpio/unexport
echo "8" > /sys/class/gpio/unexport
echo "7" > /sys/class/gpio/unexport
まぁ、5ビットの拡張出力ビットがあれば、8ビット幅のラッチを16個まで増設できるので、総ビットは128ビットの出力ポート個別制御まで、簡単な外部回路の増設だけで出来るので、まずまず十分だろう。 ただしLinuxという重いシステムのRaspberry Piで、チラつかない十分なスピードで128個を個別にPWM制御できるかどうかは、実験してみないと判らない。

YouTube

ここで、汎用入出力の8ビットの他の7ビットの名前を以下のように変更して、当面はこの表で行くことにした。 I2Cの2ビットは入力の拡張用として、SPIの5ビットを出力の拡張に利用する作戦である。

ピン番号     信号名     ソフト上の呼び名
11GPIO 0GPIO17
12GPIO 1GPIO18
13GPIO 2GPIO21
15GPIO 3GPIO22
16GPIO 4GPIO23
18GPIO 5GPIO24
22GPIO 6GPIO25
7GPIO 7GPIO4
8TxGPIO14
10RxGPIO15
3Ex_In 0GPIO0
5Ex_In 1GPIO1
19Ex_Out 0GPIO10
21Ex_Out 1GPIO9
23Ex_Out 2GPIO11
24Ex_Out 3GPIO8
26Ex_Out 4GPIO7
(この表は後で改訂されている事に注意)

そして、午前中にふーみんに出していたメイルに、昼過ぎに以下のように返信があった。 さっそくBlink(1)と、せっかくなのでSlice基板もプレゼントとして発送手配した。 しかしやはり、このビン番号の対応は大混乱の模様である。(^_^;)

ちらっと読みましたが,現物を提供していただけるのであれば
手元でやってみた方が早そうです^^;

ちなみにピン番号の対応のごちゃごちゃさは,私の本の中でも
かなり強調して書いたつもりです^^;

・今後,RPiのRevisionが変わるとまた変わる可能性があると明言されている
・ボードの番号,ハード仕様の番号,OSレベルの番号,Wiringの番号,が,
  すべて規則性なくバラバラ

という素敵な状態ですが,magic numberを書かず,定数定義して
シンボリックに使いなさい,ということなのでしょう.

Systematicにやるなら,各言語ごとに BOARD_3V3_1,
OS_3V3_1,WIRING_3V3_1,などのようにマイヘッダを
一度定義してしまえば,あとはそれを使う,という感じでもやれるとは
思いますが,あ,リビジョンによっても違うからそれも定義名に入れないと
だめか・・・orz
ここで昼休みが終わり、アポのあった及川さんと、総合演習IIの映像作品の音楽について相談した。 今月中、という新たな目標が出来たが、来月末の完成が楽しみである(^_^)。 さて、ふーみん本の続きでは、「温度センサのデータをI2Cで取得する」というトピックである。 しかし僕はI2Cは使うつもりが無いので、ここはサクサクとスキップである。 ただし、defaultではライブラリもインストールされておらず、環境としても登録されていないI2Cをどうやって使うか、というのは、他のケースでも(もしかするとBlink(1)でも)関係するので、以下のようにメモとして整理しておいた。 ここからしばらくは、取得した温度データの16進数の話、Perlでのデータ変換、全体をシェルスクリプトでまとめる、というお話で軽くスルーできて、(校正原稿の)136ページまでジャンプした。 この次の「定期的な実行」というのは、Raspberry PiというよりもUnixの機能だが、チラッと見てみると「cron」を使っていて「分」単位の起動とあり、さらにWebサーバ(apache)を起動して外部からセンサデータを見る・・・という方向の話だったので、ここも今回はバスする事にした。

その次のトピックは「大気圧センサのデータを SPIで取得する」というもので、データの変換にはPythonを使っているが、なんせSPIのポートをハードウェア拡張に使用する、という方針のため(^_^;)、ここもパスとなった。 その次のトピックは(校正原稿の)162ページからで、「WebカメラをUSBに繋いでストリーミングと動体検知」というものである。 これは面白そうなので、明日以降にチェックしてみることにした。


「Raspberry Pi日記」トップに戻る