特集 : 最新組み込みLinux実践講座Part5

対象製品: Armadillo-500Armadillo-300Armadillo-9Armadillo-240Armadillo-230Armadillo-220Armadillo-210Armadillo(HT1070)Armadillo-J
先頭 Part 4 Part 6

(株)アットマークテクノ
中井真大
NAKAI Masahiro

この文書は技術評論社「Software Design」2007年11月号に掲載されたものです。

本章では,アンケートツールで使用する名刺スキャナを動かす部分とタッチパネルについて説明します.

USB 名刺スキャナ

本章では,USB名刺スキャナをLinuxで動かすことができるようにプログラムを作成します.今回使用する名刺スキャナは「Q-Scan A6 Scanner」です(写真1).これを動かすために,libusb とsaneを使用します.

●写真1 Q-Scan A6 Scanner

libusb

libusb の導入

libusb(http://libusb.wiki.sourceforge.net/)は,ユーザアプリケーションレベルでUSB デバイスにアクセスするためのものです.開発PC にlibusb をインストールするには,libusb のARM 用パッケージをクロス開発用に変換し,変換したクロスパッケージをインストールします(図1).

●図1 ARM クロスlibusb パッケージのインストール

$ cd  ~/work.sd
$ wget http://ftp.jp.debian.org/debian/pool/main/libu/libusb/libusb-0.1-4_0.1.12-5_arm.deb
$ wget http://ftp.jp.debian.org/debian/pool/main/libu/libusb/libusb-dev_0.1.12-5_arm.deb
$ dpkg-cross -a arm -b libusb-0.1-4_0.1.12-5_arm.deb libusb-dev_0.1.12-5_arm.deb
# dpkg -i libusb-0.1-4-arm-cross_0.1.12-5_all.deb libusb-dev-arm-cross_0.1.12-5_all.deb

libusb を使用したサンプル

ここでは,libusb を使用したUSB キーボードの入力を取得する簡単なサンプルコード(リスト1)を用いて,libusbの使い方を解説します.サンプルプログラムでは,USB デバイスの中で最初に見つかったUSB キーボードのキー入力を出力します.ESCをタイプすると終了します.

●リスト1 libusb のサンプルコード

  1: #include <stdio.h>
  2: #include <usb.h>
  3: #include <errno.h>
  4:
  5: static unsigned char keycode[256] = {
  6:   ' ',' ',' ',' ','a','b','c','d','e','f','g','h','i','j','k','l',
  7:   'm','n','o','p','q','r','s','t','u','v','w','x','y','z','1','2',
  8:   '3','4','5','6','7','8','9','0',' ',' ',' ',' ',' ',' ',' ',' ',
  9:   ' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',
 10:   ' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',
 11:   ' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',
 12:   ' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',
 13:   ' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',
 14:   ' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',
 15:   ' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',
 16:   ' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',
 17:   ' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',
 18:   ' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',
 19:   ' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',
 20:   ' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',
 21:   ' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',
 22: };
 23:
 24: void
 25: print_input(unsigned char *buf, uint16_t size)
 26: {
 27:         int i;
 28:
 29:         if (size < 3) return;
 30:         if (buf[2] == 0x01) return;
 31:
 32:         printf("INPUT: ");
 33:         for (i=2; i<size; i++) {
 34:                 if (buf[i])
 35:                         printf("\"%c\"(0x%02x) ", keycode[buf[i]], buf[i]);
 36:         }
 37:
 38:         printf("%s%s\n",
 39:                (buf[0] & 0x01) ? "[ctrl]":"",
 40:                (buf[0] & 0x02) ? "[shift]":""
 41:               );
 42: }
 43:
 44: int
 45: usb_keyboard_test(struct usb_device *dev)
 46: {
 47:         usb_dev_handle *handle;
 48:         struct usb_config_descriptor *config = &dev->config[0];
 49:         struct usb_interface *interface = &config->interface[0];
 50:         struct usb_interface_descriptor *altsetting = &interface->altsetting[0];
 51:         struct usb_endpoint_descriptor *endpoint = &altsetting->endpoint[0];
 52:         uint8_t ep = endpoint->bEndpointAddress;
 53:
 54:         unsigned char buf[256];
 55:         ssize_t read_size;
 56:         int ignore = 1;
 57:
 58:         handle = usb_open(dev);
 59:         usb_get_string_simple(handle, dev->descriptor.iProduct,
 60:                               (char *)buf, sizeof(buf));
 61:
 62:         printf("USB-DEV: 0x%04x/0x%04x \"%s\"\n",
 63:                dev->descriptor.idVendor, dev->descriptor.idProduct, buf);
 64:
 65:         if (usb_set_configuration(handle, config->bConfigurationValue) < 0) {
 66:                 if (usb_detach_kernel_driver_np(handle,
 67:                                                 altsetting->bInterfaceNumber) < 0) {
 68:                         printf("usb_set_configuration() error.\n");
 69:                         usb_close(handle);
 70:                         return -1;
 71:                 }
 72:         }
 73:
 74:         usb_claim_interface(handle, altsetting->bInterfaceNumber);
 75:
 76:         while (1) {
 77:                 read_size = usb_interrupt_read(handle, ep, (char *)buf,
 78:                                                endpoint->wMaxPacketSize, 1000);
 79:                 if (read_size < 0) {
 80:                         if (read_size == -ETIMEDOUT)
 81:                                 printf("read timeout.\n");
 82:                         else
 83:                                 printf("read error: %d\n", read_size);
 84:                 }
 85:
 86:                 if (!ignore || buf[2])
 87:                         print_input(buf, endpoint->wMaxPacketSize);
 88:
 89:                 if (buf[2] != 0)
 90:                         ignore = 0;
 91:                 else
 92:                         ignore = 1;
 93:
 94:                 if (buf[2] == 0x29)
 95:                         break;
 96:         }
 97:
 98:         usb_resetep(handle, ep);
 99:         usb_release_interface(handle, altsetting->bInterfaceNumber);
100:
101:         usb_close(handle);
102:         return 0;
103: }
104:
105: int
106: check_keyboard(struct usb_device *dev)
107: {
108:         struct usb_config_descriptor *config = &dev->config[0];
109:         struct usb_interface *interface = &config->interface[0];
110:         struct usb_interface_descriptor *altsetting = &interface->altsetting[0];
111: 
112:         if (altsetting->bInterfaceClass == 3 &&
113:             altsetting->bInterfaceSubClass == 1 &&
114:             altsetting->bInterfaceProtocol == 1)
115:                 return 1;
116:         return 0;
117: }
118:
119: int main(void)
120: {
121:         struct usb_bus *bus;
122:         struct usb_device *dev;
123:
124:         usb_init();
125:
126:         if (!usb_get_busses()) {
127:                 usb_find_busses();
128:                 usb_find_devices();
129:         }
130:
131:         for (bus = usb_get_busses(); bus; bus = bus->next) {
132:                 for (dev = bus->devices; dev; dev = dev->next) {
133:                         if (dev->descriptor.idVendor != 0 || dev->descriptor.idProduct != 0) {
134:                                 if (check_keyboard(dev)) {
135:                                         usb_keyboard_test(dev);
136:                                         return 0;
137:                                 }
138:                         }
139:                 }
140:         }
141:         return 0;
142: }

  • usb_keyboard_test 関数(44~103行目)

    USB デバイスをオープンし,usb_get_string_simple でプロダクト名を取得しています.パケットの送受信を行う場合は,usb_claim_interfaceを実行しなければなりません.USB キーボードは,Transfer TypeがInterruptのため,パケットを読み込むときはusb_interrupt_readを使用します.

  • check_keyboard 関数(105~117行目)

    インターフェースクラスが“3”(Human Interface Devices),インターフェースサブクラスが“1”,インターフェースプロトコルが“1”(Keyboard)である場合に,TRUEを返します.

  • libusb の初期化(124~129行目)

    libusbを初期化します.

  • USB デバイスの検索(131~140行目)

    USBデバイスを検索し,ルートHUB(ベンダIDとプロダクトIDがともに“0”)以外のデバイスに対して,USB キーボードであるかチェックを行います.USB キーボードであった場合は,テスト関数をコールします.

sane

sane(Scanner Access Now Easy)とは,スキャナやカメラなどのラスタイメージを取り込むハードウェアに対するアクセスを標準化したAPIです.詳しくは,sane のWeb サイト(http://www.sane-project.org/)を参照してください.

sane-backends をAtmark Dist へ組み込む

今回使用するsane-backends のバージョンは,「1.0.18」です.本特集4 章でDirectFB を組み込んだときと同じように,Atmark Distへsane-backendsを組み込みます.

まずは,ディレクトリの作成とソースコードを展開します(図2).次に,Atmark Distでビルドすることができるようにビルドシステムの変更を行います(リスト2,3).

●図2 sane-backends をAtmark Dist へ組み込む

$ cd ~/work.sd
$ wget http://alioth.debian.org/frs/download.php/1669/sane-backends-1.0.18.tar.gz
$ cd atmark-dist/user
$ mkdir sane-backends
$ cd sane-backends
$ tar zxf ../../../sane-backends-1.0.18.tar.gz
●リスト2 config/config.in の修正個所

(略)
830: comment 'Graphics tools'
831: bool 'imagemagick'              CONFIG_USER_IMAGEMAGICK
832: bool 'sane-backends'            CONFIG_USER_SANEBACKENDS_SANEBACKENDS        追加
833: if [ "$CONFIG_USER_SANEBACKENDS_SANEBACKENDS" = "y" ]; then                  追加
834:         bool '  scanimage'               CONFIG_USER_SANEBACKENDS_SCANIMAGE  追加
835:         bool '  backend - plustek'       CONFIG_USER_SANEBACKENDS_BE_PLUSTEK 追加
836: fi                                                                           追加
837:
838: comment 'Video tools'
(略)
●リスト3 user/Makefile の修正個所

(略)
271: dir_$(CONFIG_USER_SAMBA_SMBMOUNT)             += samba
272: dir_$(CONFIG_USER_SAMBA_SMBUMOUNT)            += samba
273: dir_$(CONFIG_USER_SANEBACKENDS_SANEBACKENDS)   += sane-backends               追加
274: dir_d                                          += sane-backends               追加
275: dir_$(CONFIG_USER_SASH_REBOOT)                += sash
276: dir_$(CONFIG_USER_SASH_SH)                    += sash
(略)

さらに,sane-backends用のMakefileを作成します(リスト4).

●リスト4 atmark-dist/user/sane-backends/Makefile

 1: #
 2: # Makefile for sane-backends
 3: #
 4:
 5: -include $(CONFIG_CONFIG)
 6:
 7: SANE_BACKENDS_DIR = $(shell pwd)
 8:
 9: SRC_DIR = sane-backends-1.0.18
10: INSTALL_DIR = preinstall
11:
12: MAKEHOST = ${shell $(SRC_DIR)/config.guess}
13: MAKETARGET = ${shell $(SRC_DIR)/config.sub $(CROSS)gnu}
14: CONF_OPT = --disable-translations --disable-shared
15: CONF_OPT_MISC =
16:
17: TARGET_SUBDIRS = include lib sanei backend frontend
18:
19: backend_y =
20: backend_$(CONFIG_USER_SANEBACKENDS_BE_PLUSTEK) += plustek
21:
22: all: build
23:
24: $(SRC_DIR)/Makefile:
25:         @if [ "$(backend_y)" = "" ]; then \
26:                 echo "Error (1): No backends selected." ;\
27:                 exit 1 ;\
28:         fi
29:
30:         if [ ! -e $(SRC_DIR)/Makefile ]; then \
31:                 (cd $(SRC_DIR); \
32:                  ./configure --host=$(MAKETARGET)\
33:                              $(CONF_OPT) $(CONF_OPT_MISC)\
34:                              BACKENDS="$(backend_y)"\
35:                 );\
36:         fi
37:
38: build: $(SRC_DIR)/Makefile
39:         make -C $(SRC_DIR) SUBDIRS="$(TARGET_SUBDIRS)"
40:         mkdir -p $(INSTALL_DIR)
41:         make -C $(SRC_DIR) \
42:                 prefix=$(SANE_BACKENDS_DIR)/$(INSTALL_DIR)/usr/local \
43:                 SUBDIRS="$(TARGET_SUBDIRS)" \
44:                 install
45:
46: romfs: build
47:         $(ROMFSINST) -e CONFIG_USER_SANEBACKENDS_SCANIMAGE \
48:                      $(INSTALL_DIR)/usr/local/bin/scanimage /bin/
49:
50: clean:
51:         make -C $(SRC_DIR) clean
52:
53: distclean: clean
54:         make -C $(SRC_DIR) distclean
55:         rm -rf $(INSTALL_DIR)
56:

これでビルドする準備はできましたが,実はsane-backends には,今回使用するQ-Scan A6 Scanner 用のバックエンドが実装されていません.バックエンドを追加するには,sane-backendsにパッチを当てます(図3).

●図3 sane-backends にパッチを当てる

$ cd ~/work.sd
$ wget http://download.atmark-techno.com/misc/softwaredesign_2007-11/chapter5/sane-backends_q-scan-a6-scanner.patch
$ cd atmark-dist
$ patch -p1 < ../sane-backends_q-scan-a6-scanner.patch

このパッチは,Windows で動作させたときの内部レジスタ設定をリバースエンジニアリングによりコード化したものです.リバースエンジニアリングには,オービット製USBプロトコルアナライザ「US-H200」
http://www.aubit.co.jp/US-H200_gaiyou.html,写真2)を使用しました.

●写真2 US-H200

scanimage の動作確認

ソースコードの準備ができたので,Atmark Distのコンフィギュレーションを行いビルドします(図4).ビルドが終了したら,saneのフロントエンドである「scanimage」コマンドを使って動作確認してみましょう.

●図4 コンフィギュレーションとビルド

$ make menuconfig
Kernel/Library/Defaults Selection  --->
        [*] Customize Vendor/User Settings
        
Miscellaneous Applications  --->
        [*] imagemagick
        [*] sane-backends
        [*]   scanimage
        [*]   backend - plustek
        
$ make

DirectFB と同様,できあがったイメージをフラッシュメモリに書き込めば新しいイメージで動作します.ですが,scanimageを実行するうえで必要となるファイルは少ないため,今回はFTP を使って転送し動作確認を行ってみます(図5).

●図5 scanimage の動作確認

Armadillo-500 # cd /home/ftp/pub
Armadillo-500 # ls
libusb-0.1.so.4  scanimage
Armadillo-500 # mv libusb-0.1.so.4 /lib
Armadillo-500 # chmod a+x scanimage
Armadillo-500 # ./scanimage > scan-test.pnm
※作業PC からArmadillo-500 にscanimage とlibusb-0.1.so.4 を転送します.libusb-0.1.so.4 は,
  作業PC の/usr/arm-linux/lib/ディレクトリのものを使用してください.

スキャナで読み取った画像を作業PC に転送し,画像を確認してみましょう(図6).

●図6 スキャン画像

scanimageをオプションなしで実行すると,余分な領域まで読み取ってしまうことがわかります.これを名刺サイズに合わせるには,scanimageのオプションを設定します.また,名刺の情報を読み取るには少し解像度が小さいので,解像度についても変更します(図7,8).

●図7 オプションを指定してscanimage を実行

Armadillo-500 # ./scanimage --resolution 150 -l 4 -x 95 -y 62 > scan-test2.pnm
●図8 名刺サイズに最適化した場合のスキャン画像

画像変換

scanimageで作成できる画像ファイルのフォーマットは,PNM またはTIFF です.DirectFB ではこれらのファイルフォーマットに対応していないので,ImageMagickを使用して画像変換を行います.

ImageMagick のAPI を使用して直接プログラムから変換することもできますが,今回はImageMagickに含まれるconvertコマンドを使用して画像フォーマット変換をしてみましょう(図9).

●図9 PNM ファイルをJPG ファイルに変換する例

Armadillo-500 # convert image.pnm image.jpg

タッチパネル

Quixun QT-701AV-S を使う

今回使用するディスプレイは,「Quixun QT-701AV-S」です(写真3).これはタッチパネルに対応していて,USB からその入力情報を取得することができます.Armadillo-500のLinuxカーネルで使用する場合は,図10のようにコンフィギュレーションする必要があります.

●写真3 Quixun QT-701AV-S

●図10 Armadillo-500 環境でのコンフィギュレーション

Kernel/Library/Defaults Selection  --->
        [*] Customize Kernel Settings

Device Drivers  --->
        USB support  --->
                <*> USB Touchscreen Driver
                [*]   eGalax device support

位置情報が正しくなるよう修正

しかし,このままでは位置情報がおかしな状態になります(図11).図から判断すると,

  • x 軸とy軸が逆になっている
  • y軸の加算方向が逆になっている
  • x 軸,y 軸にオフセットがあり,また最大値もおかしい

ということがわかります.

●図11 正常な場合と未改造の場合の位置情報

さっそくソースコードを見てみましょう.該当するソースコードは,linux-2.6.x/drivers/usb/input/usbtouchscreen.c です.133 ~ 144 行目のegalax_read_data関数が位置情報に変換する関数です(リスト5).

●リスト5 修正前のソースコード

133: static int egalax_read_data(unsigned char *pkt, int *x, int *y, int *touch, int *press)
134: {
135:         if ((pkt[0] & EGALAX_PKT_TYPE_MASK) != EGALAX_PKT_TYPE_REPT)
136:                 return 0;
137:
138:         *x = ((pkt[3] & 0x0F) << 7) | (pkt[4] & 0x7F);
139:         *y = ((pkt[1] & 0x0F) << 7) | (pkt[2] & 0x7F);
140:         *touch = pkt[0] & 0x01;
141:
142:         return 1;
143:
144: }

138~139行目でx,yの位置情報を取得しています.これを逆に取得することでx軸,y軸の修正はできます.残りの修正は,修正後のリストを見るとわかると思います(リスト6).

●リスト6 修正後のソースコード

133: static int egalax_read_data(unsigned char *pkt, int *x, int *y, int *touch, int *press)
134: {
135:         if ((pkt[0] & EGALAX_PKT_TYPE_MASK) != EGALAX_PKT_TYPE_REPT)
136:                 return 0;
137:
138: #if !defined(CONFIG_USB_TOUCHSCREEN_EGALAX_QT701AVS)                         追加
139:         *x = ((pkt[3] & 0x0F) << 7) | (pkt[4] & 0x7F);
140:         *y = ((pkt[1] & 0x0F) << 7) | (pkt[2] & 0x7F);
141: #else                                                                        追加
142:         {                                                                    追加
143:                 int _x, _y;                                                  追加
144:                 _x = ((pkt[1] & 0x0F) << 7) | (pkt[2] & 0x7F);               追加
145:                 _y = ((pkt[3] & 0x0F) << 7) | (pkt[4] & 0x7F);               追加
146:                                                                              追加
147:                 _x = ((_x - 90) < 0 ? 0 : (_x - 90));                        追加
148:                 _y = ((1900 - _y) < 0 ? 0 : (1900 - _y));                    追加
149:                                                                              追加
150:                 *x = (int)((CONFIG_INPUT_TSDEV_SCREEN_X * _x) / 1850);       追加
151:                 *y = (int)((CONFIG_INPUT_TSDEV_SCREEN_Y * _y) / 1750);       追加
152:         }                                                                    追加
153: #endif                                                                       追加
154:         *touch = pkt[0] & 0x01;
155:
156:         return 1;
157:
158: }

CONFIG_INPUT_TSDEV_SCREEN_X ,CONFIG_INPUT_TSDEV_SCREEN_Yは,タッチスクリーンインターフェースの解像度が設定されていて,xとyの値をまるめるために使用しています.また,この修正はeGalaxのすべてに反映させたくないのでCONFIG_USB_TOUCHSCREEN_EGALAX_QT701AVSが有効になっている場合にのみ有効となるようにします.linux-2.6.x/drivers/usb/input/Kconfigを変更します(リスト7).

●リスト7 linux-2.6.x/drivers/usb/input/Kconfig

219: config USB_TOUCHSCREEN_EGALAX
220:         default y
221:         bool "eGalax device support" if EMBEDDED
222:         depends on USB_TOUCHSCREEN
223:        
224: config USB_TOUCHSCREEN_EGALAX_QT701AVS                                     追加
225:         default n                                                          追加
226:         bool "Adjustment of QT-701AV-S" if EMBEDDED                        追加
227:         depends on USB_TOUCHSCREEN_EGALAX && INPUT_TSDEV                   追加
228:
229: config USB_TOUCHSCREEN_PANJIT

これで,位置情報を修正したドライバをビルドできるようになりました.コンフィギュレーションを行い,修正を有効にします(図12).

●図12 コンフィギュレーション

Device Drivers  --->
        Input device support  --->
                  <*>   Touchscreen interface
                  (640)   Horizontal screen resolution
                  (480)   Vertical screen resolution
        USB support  --->
                  <*> USB Touchscreen Driver
                  [*]   eGalax device support
                  [*]     Adjustment of QT-701AV-S
COLUMN

楽チン作業♪

この章で解説した作業を簡単に行うには,図A のようにします.

●図A 本章の作業手順まとめ

$ cd ~/work.sd
クロスライブラリのインストール
$ wget http://ftp.jp.debian.org/debian/pool/main/libu/libusb/libusb-0.1-4_0.1.12-5_arm.deb
$ wget http://ftp.jp.debian.org/debian/pool/main/libu/libusb/libusb-dev_0.1.12-5_arm.deb
$ dpkg-cross -a arm -b libusb-0.1-4_0.1.12-5_arm.deb libusb-dev_0.1.12-5_arm.deb
dpkg -i は特権ユーザで実行
# dpkg -i libusb-0.1-4-arm-cross_0.1.12-5_all.deb libusb-dev-arm-cross_0.1.12-5_all.deb
$
$ wget http://alioth.debian.org/frs/download.php/1669/sane-backends-1.0.18.tar.gz
$ wget http://download.atmark-techno.com/misc/softwaredesign_2007-11/chapter5/atmark-dist-20070727_add_user_sane-backends.patch
$ wget http://download.atmark-techno.com/misc/softwaredesign_2007-11/chapter5/sane-backends_qscan-a6-scanner.patch
$ wget http://download.atmark-techno.com/misc/softwaredesign_2007-11/chapter5/linux-2.6.18-12-at0_fix_ts-qt701avs.patch
$ cd linux-2.6.18-12-at0
$ patch -p1 < ../linux-2.6.18-12-at0_fix_ts-qt701avs.patch
$ cd ../atmark-dist-20070727
$ patch -p1 < ../atmark-dist-20070727_add_user_sane-backends.patch
$ (cd user/sane-backends/; tar zxf ../../../sane-backends-1.0.18.tar.gz)
$ patch -p1 < ../sane-backends_q-scan-a6-scanner.patch
本文図4 を参照
$ make menuconfig
$ make
先頭 Part 4 Part 6