『ルーターにつなぐ』I2C温度センサ編

本記事はルーターハックAdvent Calendar 13日目の記事です。12日枠の記事は srchack さんの「LinkIt 7688のビルド手順。(Chaos Calmer 15.05.1)」です。バリバリ遅れてるところに助け舟を出していただいてほんとありがとうございます。

www.srchack.org

今回は、さまざまなペリフェラルを実装しているルーターのために、元々なかったパーツをつないで遊んでしまおうという企画『ルーターにつなぐ』の第2弾だ。

おことわり

技適 に関する筆者の配慮や考えについてはカレンダー1日目「技適とルーターハック」をご覧ください。本記事で紹介する内容は、法を遵守するための慎重な注意をもって書かれています。

www.zopfco.de

I2C とは

アイスクエアドシーとか、アイツーシーとか読まれるシリアルバスの一種。クロックが低速(100kHz〜400kHzが普通)でこれを搭載しないマイコンはないというほどポピュラー。

I2C をしゃべることのできるセンサーを Raspberry Pi につないだことがあるという方も多いだろう。

バスなので1つの信号線に複数のデバイスが接続されるが、スレーブの区別は SPI とは異なっていて、データ線を流れる信号にスレーブアドレスも織り込まれる形式をとっている。これにより、データ (SDA) と クロック (SCL) の2本のみという非常に少ない信号線で通信ができるようになっている。

ルーターで I2C を使うには

今回は、やはり前回に引き続き 多摩電子工業 (Axing) W06 を題材に使うことにする。

ルーターで I2C を使うには、Device Tree の i2c ノードを有効化して Linux kernel に認識させることになる。

ファーム準備

ここから、

  • I2C 関連ツール用意
  • Device Tree の変更
  • I2C の信号が出ている回路を探す

の3つのステップを経て I2C を使えるようにする。

I2C 関連ツール用意

ファームの config に以下追加する。

まず、i2cdetect などが入っているi2c-tools。

  • Utilities -> i2c-tools

カーネルで I2C を扱えるようにするモジュール類。

  • Kernel Modules -> I2C support -> kmod-i2c-core
  • Kernel Modules -> I2C support -> kmod-i2c-mt7628

watch コマンドがあった方が便利だ。

  • Basy system -> busybox -> Process Utilities -> watch

Device Tree の変更

W06 の Device Tree は、 openwrt/target/linux/ramips/dts/W06.dts に入っている。この中で mt7628an.dtsi という SoC 共通の dtsi (include用の dts は拡張子として dtsi がつく) を include している。

mt7628an.dtsi は汎用なものなので、ルーターで使わない I2C のノードも記載されているものの標準で disabled になっている。include する側、つまり W06 の dts でこれを有効化する。

W06.dts を開き、I2C のノードを okay にするように以下追記する。

diff --git a/target/linux/ramips/dts/W06.dts b/target/linux/ramips/dts/W06.dts
index 8c3bbe4058..cd70ebbb2f 100644
--- a/target/linux/ramips/dts/W06.dts
+++ b/target/linux/ramips/dts/W06.dts
@@ -66,6 +66,10 @@
        };
 };

+&i2c {
+    status = "okay";
+};
+
 &spi0 {
        status = "okay";

I2C の信号が出ている回路を探す

探りを入れる前に、ひとまず i2cdetect してみよう。このコマンドを使うと、I2Cの各スレーブアドレスに対して応答を要請することができる(しくみは後述)。応答のあったアドレスはそのアドレス自体が表示され、応答がないと -- になる。

root@OpenWrt:~# i2cdetect -y 0
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: 30 31 32 33 34 35 36 37 -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

What? 何も繋がってないはずなのに亡霊がいる。オシロで探りを入れるしかないな。 watch -n 0.5 i2cdetect -y 0 でずっと信号が出るようにしよう。

Every 1s: i2cdetect -y 0                                                                                                                                                   2018-12-09 13:45:51

     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: 30 31 32 33 34 35 36 37 -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --

データシート曰く、I2C は21ピンが SDA、20ピンが SCL らしい。近くに先日いじった SPI のピンも見える。

f:id:puhitaku:20181218005741p:plain

実際の写真はこれ。該当するピンと、その配線の先にあるチップ抵抗のランドをピンクで示した。

f:id:puhitaku:20181218005608p:plain

よく見ると…どちらのピンも、共通のランドにチップ抵抗で接続されている。もしや… I2Cのために最初からPull-upしてくれてるのか!!?*1

21ピンの SDA からのびた先のチップ抵抗に試しにプローブを当ててみる。

f:id:puhitaku:20181218004447j:plain

f:id:puhitaku:20181218004823p:plain
SDAぽい。

うんうん、それっぽい!

なんと、ご丁寧にも I2C の配線は最初からPull-upされていた。なんて親切なルーターなんだ(感激)

あっさり I2C の配線が特定できたが、本来出ない亡霊が i2cdetect で出てきているのが気になる。配線をこのランドから伸ばし、ロジアナに接続してみる。

信号をより詳細に計測する

ロジアナで詳しく調べる前に、I2C の probe のしくみを解説しよう。

I2C では、スレーブアドレス および レジスタアドレス を使ってアドレッシングする。

  • スレーブアドレス = バスに繋がれたデバイスを特定する値
  • レジスタアドレス = デバイス内のレジスタを特定する値

マスターが何らかの値を送るときには、まずスレーブアドレスを流し、そして書き込みたいレジスタアドレスを流し、最後に入れたいデータを流すことになる。これをビット単位で表したのが以下の図である。

f:id:puhitaku:20181222155653p:plain
出典: TI - Understanding the I2C Bus http://www.ti.com/lit/an/slva704/slva704.pdf

示されている各ビットは、以下のようなタイミングで表現され、伝送される。クロックとデータのエッジが重なる SPI などとは異なり、クロック (SCL) が High である時間をいわば前後に「包み込む」ようにしてデータ (SDA) のエッジが描かれるのが I2C の特徴である。

f:id:puhitaku:20181222160041p:plain
出典: 同上

ここで、上記2つの図で ACK と表現されているビットが、スレーブ側と正しくコミュニケーションできているかどうかを示す。このビットの区間だけは、マスターは SDA をコントロールしていない。つまり、スレーブ側が意図的に Low に落とせば「スレーブは指示を理解した」とマスター側に伝えられることになる。

よって、ACK に相当する区間が Low であれば ACK (OK)、High であれば NACK (NG) となる。

f:id:puhitaku:20181222161333p:plain
出典: 同上 ACKビットが High のため、スレーブに指示が伝わっていない = NACK となる。

それでは、本来居ないはずのアドレス 0x30 を実際に i2cdetect した時のスクリーンショットを見てみよう。Digilent - Analog Discovery 2 に接続し、Scope(オシロ) と Logic (ロジアナ)の両方で観察したのが以下のスクリーンショット。上が Scope、下が Logic。

f:id:puhitaku:20181222154844p:plain

まず、SDA が Low に落ちて START condition になる。次に、アドレス 0x30 を Read したいと伝える。さらにその次が件の ACK になるのだが、High のままになっているため NACK (NCK) を示している。

電気回路レベルでは特に問題がないのだが、やはり i2cdetect は 0x30 を検知したように表示している。

Every 1s: i2cdetect -y 0 0x30 0x30                                                                                                                                         2018-12-14 03:56:11

     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                                                 
10:                                                 
20:                                                 
30: 30                                              
40:                                                 
50:                                                 
60:                                                 
70:

回路構成はどうだろうか?リファレンスとなる LinkIt Smart 7688 の Schematic から I2C 部分を抜粋したのが以下の画像だ。

f:id:puhitaku:20181222163025p:plain
出典: LinkIt Smart 7688 Resources - Documentation https://docs.labs.mediatek.com/resource/linkit-smart-7688/en/documentation

Pull-up抵抗は 4.7 kΩ となっている。W06 の表面実装抵抗にテスターを当てたところ、やはり 4.7 kΩ を示したため、これも問題ないようだ。う〜んわかんなくなってきたぞw

ドライバにパッチを当てる

電気的に問題が無さそうなのに i2cdetect が false-positive を示すということは、SPI の時と同じく物理層以上ソフトウェア以下で問題がありそうである。早速 OpenWrt の Issue/PR を検索すると…

あった!

github.com

クロックの扱いや ACK のハンドリングがダメだったらしい。この PR で出ているパッチは Upstream (Mainline) に出すとだけ書かれて閉じられているが、Fork 先を git add remote からの fetch して cherry-pick すればよい。

パッチを当てると静かになった。行けそう。

root@OpenWrt:~# i2cdetect -y 0
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --

温度センサを繋げてみる

I2C の亡霊問題は大丈夫っぽいので実際に何か繋ごう。今回は秋月で雑に買った 高精度温度センサ STTS751 を接続する。

akizukidenshi.com

ただ、あまりにも無計画に買ったのでDIP化基板を買い忘れてしまった。カプトンで固定して配線。そしてブレッドボードへ。

f:id:puhitaku:20181225221318j:plain
ルーペで撮った。

f:id:puhitaku:20181225223245p:plain
データシートより

左の点がある足から、反時計回りに1〜6ピンがのびる。4ピンはデータシート曰く may not float らしいがなくてもそれっぽく動くので今回はよしとした。

1ピンを Pull-up する抵抗の値によってアドレスが決まる。テストのため 10k + 10k = 20kΩ を接続したところ、データシートどおり 0x38 で認識された。

root@OpenWrt:~# i2cdetect -y 0
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- 38 -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --

計測してみる

温度レジスタの構成は以下のようになっている。0x00 の値を読むだけでも1℃単位の温度が得られるので、今回は簡易的に 0x00 の値だけ読むことにする。

f:id:puhitaku:20181225223408p:plain
データシートより

i2cdetect の兄弟に i2cget があるのでそれで読んでみる。

root@OpenWrt:~# i2cget -y 0 0x38 0
0x13

19℃。部屋にある別の温度計曰く19.6℃なので正しそうだ。指で温めると温度が上がり、離すと下がることも確認できた。

やったぜ!!!!!!!

次回

次回というか、Advent Calendarではない更新でこれからもこういったネタを投稿していけたらと思う。今回は以上!

*1:I2Cは上りも下りも単一の信号線で通信するため、ホストとスレーブどちらでも信号線を操作できるようにPull-upされている。このような、信号線上の誰かがLowを出力すると全体がLowになるような回路構成をワイヤードORという。

『ルーターにつなぐ』SPI液晶編

本記事はルーターハックAdvent Calendar 11日目の記事です。

さまざまなペリフェラルを実装しているルーターのために、元々なかったパーツをつないで遊んでしまおうという企画『ルーターにつなぐ』の第2弾。今回は、ルーターに小さな SPI 液晶をつないで遊ぶことにする。遊ぶといっても、配線からカーネルパッチから結構大変なのでやりごたえ抜群だ。

おことわり

技適 に関する筆者の配慮や考えについてはカレンダー1日目「技適とルーターハック」をご覧ください。本記事で紹介する内容は、法を遵守するための慎重な注意をもって書かれています。

www.zopfco.de

SPI とは

SPI とは Serial Peripheral Interface の略で、I2Cを開発した Philips ではなく Motorola が開発したシリアルバスだ。とにかく信号線が減らせる I2C とは思想が異なり、下り(MOSI)・上り (MISO)・デバイス選択 (Slave Select や Chip Select と呼ぶ)で別々の信号線を使うため、I2C の最速 3.4Mbps を遥かに超えるスピード (数Mbps以上、場合によっては数十Mbps) を出すことができる。

参考: SPIについて

だから、センサーのような散発的で量の少ない通信を I2C がよく担うのに対し、SPI はスループットが大きくて連続したストリームを流す用途に向いている。今回題材にする SPI 液晶以外で例えると、Arduino の Ethernet Shield は SPI 接続だ。I2C でやろうものならスループットは ISDN 程度が関の山だが、SPI だとそのレベルは余裕でクリアできるからだ。

ルーターで SPI を使うには

今回は、前回に引き続き 多摩電子工業 (Axing) W06 を題材に使うことにする。

このルーターでは、周辺デバイスの接続や内部ペリフェラルの有効無効を Device Tree で管理できる。Device Tree は、 ARM アーキテクチャのマシン固有コードが増大することに業を煮やした Linus がキレたことに端を発して開発されたものらしい。

Device Tree を簡単に説明すると、「ボードや SoC 毎に別個の初期化コードを書くのではなく、そのボードや SoC のハードウェア構成を表現できるツリーの情報をもとに、ドライバ側で必要な初期化を行う」コンセプトだ。今では ARM はもちろんのこと、MediaTek MT7628AN など ramips な SoC をはじめとして様々なプラットフォームが対応している。

この前 USB を生やした ar71xx は対応していないのだが、Device Tree で初期化できるようにするために新しいアーキテクチャ ath79 への移植が進んでいる。

前置きが長くなってしまったが、ルーターで SPI を使うには、この Device Tree の情報を変更して新たな SPI 機器を Linux kernel に認識させることになる。

ファーム準備

以下の手順で有効化する。

  • Kernel Config の変更
  • Device Tree の変更
  • SPI の信号が出ている回路を探す
  • 接続する

Kernel Config の変更

kernel_menuconfig にて以下追加する。結構多い。

  • Device Drivers
    • Character Devices
      • Virtual Terminal
    • Graphics Support
      • Frame buffer devices
        • Support for frame buffer devices
        • Framebuffer foreign endiannes support (なくてもいいかも)
        • Enable Tile Blitting Support (なくてもいいかも)
      • Console display driver support
        • Framebuffer Console support
        • Map the console to the primary display device
        • Framebuffer Console Rotation (なくてもいいかも)
      • Bootup logo

SPI 液晶のコントローラーチップに合わせてドライバを選択する。筆者の手持ちの液晶は ST7735 だったため以下選択している。

  • Device Drivers -> Staging drivers -> Support for small TFT LCD display modules -> FB driver for the ST7735R LCD Controller

Device Tree の変更

W06 の Device Tree は、 openwrt/target/linux/ramips/dts/W06.dts に入っている。この中で mt7628an.dtsi という SoC 共通の dtsi (include用の dts は拡張子として dtsi がつく) を include している。

SPI は既に SPI Flash で使用されているため、Flash のノードの隣に追記することになる。解決ポイントとしては、

  • MT7688AN は SPI 物理層が1つだけなのに対して CS (Chip Select) を2つ持つ
  • FBTFT (小さい液晶向けの Framebuffer ドライバ)のDevice Treeに関する情報の不足
  • 液晶の制御に使う DC ピンと Reset ピンを GPIO で用意する

といったところに対処する必要がある。ひとまず、Device Tree へ加える変更は以下の2コミットを参照されたい。

github.com

github.com

LinkIt Smart 7688 の Device Tree を参照すると、SPI の pinctrl として spi_pinsspi_cs1_pins が関連付けてある。これを持ってくる。

続く ST7735R のノードは、FBTFT のコードを読んで必要な値をピックアップ、記述した。加えてこの中にプロパティとして reg = <1>; を指定することで、m25p80 と同じ SPI バスの CS1 に接続されていることが表現できる。

FBTFT は Raspberry Pi しか想定しないのか、単にドキュメント不足なのか、cmdline で雑にパラメータを渡す使用方法しか出ないのが難しいところだ。

DC と Reset の GPIO は、LED に伸びる GPIO 2つをそのまま使用した。電源 LED 以外ならなんでもいいが、同じ gpiochip 扱いとなる Wi-Fi と WAN の LED を今回は使用した。よって、 gpio-leds に記述してある方のノードは削除した。

SPI の信号が出ている回路を探す

前述したように、SPI はすでに SPI Flash のための配線がなされている。加えて、SPI のバスは SoC に1つしかなく、CS で通信するデバイスを切り替えることになる。つまり、Flash の配線に液晶もつないでしまえばよい。

f:id:puhitaku:20181224143812p:plain
液晶配線前のFlash周辺

f:id:puhitaku:20181224011718j:plain
液晶配線後のFlash周辺

GPIO も全部配線したのが以下。

f:id:puhitaku:20181224144626p:plain

f:id:puhitaku:20181224145117p:plain

全体図。

f:id:puhitaku:20181224014152j:plain

いざブート!!!と思いきや…

ファームもビルドできたので実機でブートしたところ、スタックトレースが大量に出て作業どころではなくなってしまった(出力の記録は失念)。発生箇所は target/linux/ramips/patches-4.14/0043-spi-add-mt7621-support.patchWARN である。

https://github.com/puhitaku/openwrt/blob/4fd788282274e132b974e074a35cf8729c72819e/target/linux/ramips/patches-4.14/0043-spi-add-mt7621-support.patch#L338

どうやら、転送前の length の合算時に 16 bytes を超えるとダメなようだ。バッファ量の制限だろうが、分割していい感じに送ってほしいものだ。

問題はこれだけなら良いが、MT7688AN の SPI ブロックは驚くべきことに ハードバグ を抱えているらしく、SPI MODE 0 以外正しく動作しないとのこと。一体どんなテストベンチを組めばこれが判明せず出荷されるのか…。ともかく、これらの問題に対して Mainline の staging driver ではパッチが当たっているようで、OpenWrt にバックポートする PR も提出されている。筆者はこの PR を見付けるより前に Onion が当てているパッチ を見つけたのでこちらを使用しているが、今後は OpenWrt 本家の PR のマージを待つか、cherry-pick して使うのが良いだろう。筆者のブランチはこちら

波形を見てみる

パッチを当てたあとの波形は以下のとおり。上が CS1、下が MOSI だ。

f:id:puhitaku:20181224155147p:plain

CS1 が High の間は、SPI Flash との通信を行っている。Low になると液晶との通信が始まる。んん〜〜いい感じ。よくできてるなあ。

リベンジ!!

f:id:puhitaku:20181224031703j:plain

やった〜〜〜〜!

カスタマイズ

画面を回転する

Device Tree に指定を入れる ことで画面が回転できる。

フォントを変える

今回使用した液晶は 128x160 と極めて小さいため、フォントも小さいものを選択しなければまともに使えない。標準で入っているフォント VGA 8x8 font はサイズが大きくデザインも暑苦しいため、他のフォントを使ったほうが良い。

f:id:puhitaku:20181224135447j:plain
VGA 8x8 font の見た目

Mac console 6x11 font は非常に小さく、かつグリフのデザインも優れているためおすすめだ。Library routines -> Select compiled-in fonts -> Mac console 6x11 font (not supported by all drivers) で選択できる。

f:id:puhitaku:20181224131802j:plain
Mac console 6x11 font の見た目

これでも狭いことには変わりないため、可読性はギリ読める程度に下がるが、より小さいフォントの選択肢もある。Library routines -> Select compiled-in fonts -> Mini 4x6 font で選択できる。

f:id:puhitaku:20181224132317j:plain
Mini 4x6 font の見た目

コンソールでシェルを使えるようにする

シェルが紐付いている (gettyが走っている) ことを示す "Please press Enter to activate this console." という文言は標準では表示されない。Framebuffer fb0tty1 に紐付けられているため、/etc/inittabtty1 で getty するように追記すればよい。

::sysinit:/etc/init.d/rcS S boot
::shutdown:/etc/init.d/rcS K shutdown
::askconsole:/usr/libexec/login.sh
tty1::askfirst:/bin/sh --login

W06 は本体に USB ポートがついているので、USB キーボードを接続すると普通にシェルが操作できるようになる。menuconfig にて以下追加する。

  • Kernel modules -> USB Support -> kmod-usb-hid

試しにちょっと使ってみよう。

f:id:puhitaku:20181224162312j:plain
vi。

f:id:puhitaku:20181224162332j:plain
Hello World。

やったぜ!!!!

次回

次は続けて I2C をつなぐ。乞うご期待。SPI 液晶の応用もやりたいけどそれは後日。(投稿したの24日だけど!!!!)

【後編】ルーターのrootfsを拡張してPythonをビルドする

本記事はルーターハックAdvent Calendar 10日目の記事です。

9日目となる前回は、ルーターに生やした USB ポートに SD カードリーダーを挿し、rootfs として使えるようにしたうえで、sl をビルドするところまでを書いた。ゴールはルーター上での Python のビルドだったのだが、sl まででえらく時間がかかり気分的に記事を分けたくなったので前後編に分けてお送りする。

おことわり

技適 に関する筆者の配慮や考えについてはカレンダー1日目「技適とルーターハック」をご覧ください。本記事で紹介する内容は、法を遵守するための慎重な注意をもって書かれています。

www.zopfco.de

]

やっと Python をビルドする

前回 sl をビルドするためにビルドした ncurses は Python のビルドにおいてもあったほうが嬉しいライブラリ(必須ではないはず?)なので結果オーライ。意を決して Python へ。

落とす

落としてextractするだけで2分かかっててたいへんさきがおもいやられますね(朗らかな笑顔)

root@OpenWrt:~# wget https://www.python.org/ftp/python/3.7.1/Python-3.7.1.tgz
Downloading 'https://www.python.org/ftp/python/3.7.1/Python-3.7.1.tgz'
Connecting to 151.101.0.223:443
Writing to 'Python-3.7.1.tgz'
Python-3.7.1.tgz     100% |*******************************| 22267k  0:00:00 ETA
Download completed (22802018 bytes)
root@OpenWrt:~# time tar zxf Python-3.7.1.tgz
real    2m 18.03s
user    0m 19.11s
sys     0m 8.08s

configure

configure。3分47秒経って死。

root@OpenWrt:~/Python-3.7.1# time ./configure
...
checking for grep that handles long lines and -e... configure: error: no acceptable grep could be found in /usr/sbin:/usr/bin:/sbin:/bin:/usr/xpg4/bin
Command exited with non-zero status 1
real    3m 46.97s
user    0m 6.14s
sys     0m 8.21s

フルなgrepを入れねば。ファームをビルドするときに busybox をモリモリにすれば解決しそうだが、それは一旦置いておく。

root@OpenWrt:~/Python-3.7.1# opkg install grep
Installing grep (3.1-1) to root...
Downloading http://downloads.openwrt.org/snapshots/packages/mips_24kc/packages/grep_3.1-1_mips_24kc.ipk
Configuring grep.

configure。grep, ncurses通過の後成功。

root@OpenWrt:~/Python-3.7.1# time ./configure
...
checking for grep that handles long lines and -e... /usr/bin/grep
...
checking curses.h usability... yes
checking curses.h presence... yes
checking for curses.h... yes
checking ncurses.h usability... yes
checking ncurses.h presence... yes
checking for ncurses.h... yes
...
creating Makefile


If you want a release build with all stable optimizations active (PGO, etc),
please run ./configure --enable-optimizations


real    1h 53m 48s
user    5m 16.06s
sys     4m 26.75s

あー --enable-optimizations 忘れてた。まあいっか。ともかく! 1時間53分で configure が成功を収めただけでも喜ぼう!

make

これから一体どれだけ時間がかかるのやら。思えばdistccでも使えばよかった。まあ使うとしたら後の機会に。

root@OpenWrt:~/Python-3.7.1# time make

……ここから外出し、約12時間後に見てみてもまだビルドが終わってなかった。

おそるおそる top を見る。

Mem: 26508K used, 1332K free, 20K shrd, 316K buff, 6284K cached
CPU:   3% usr  11% sys   0% nic   0% idle  85% io   0% irq   0% sirq
Load average: 2.02 2.04 2.02 1/41 25308
  PID  PPID USER     STAT   VSZ %VSZ %CPU COMMAND
25307 25306 root     D    26144  94%  10% {cc1} /usr/lib/gcc/mips-openwrt-linux-
   97     2 root     IW       0   0%   4% [kworker/0:1]
  406     2 root     DW       0   0%   0% [usb-storage]
  121     2 root     SW       0   0%   0% [kswapd0]
25308   532 root     R     1196   4%   0% top
    7     2 root     SW       0   0%   0% [ksoftirqd/0]
25306 24639 root     S     3144  11%   0% gcc -c -Wno-unused-result -Wsign-compa
24639 24638 root     S     2204   8%   0% make
  784     1 root     S     1700   6%   0% /sbin/netifd
    1     0 root     S     1544   6%   0% /sbin/procd
  819     1 root     S     1428   5%   0% /usr/sbin/odhcpd
 1140     1 dnsmasq  S     1324   5%   0% /usr/sbin/dnsmasq -C /var/etc/dnsmasq.
  665     1 root     S     1232   4%   0% /sbin/logd -S 64
  532     1 root     S     1204   4%   0% /bin/ash --login
15256 15255 root     S     1204   4%   0% -ash
 1457     1 root     S<    1196   4%   0% /usr/sbin/ntpd -n -N -S /usr/sbin/ntpd
15311   784 root     S     1196   4%   0% udhcpc -p /var/run/udhcpc-eth1.pid -s
24638 15256 root     S     1196   4%   0% time make
  531     1 root     S     1192   4%   0% /sbin/ubusd
15255   964 root     S     1128   4%   0% /usr/sbin/dropbear -F -P /var/run/drop

VSZ 94%

これはまだマシで、ひどいときは 104% まで達していた。

比較として PC で Python をビルドすると、あともう5倍ぐらいの道のりは残されてそうだったので別のルーターに切り替えることにした。WHR-HP-GN よすまない、無理な仕事をさせてしまったな。

もっと性能の良いルーターで再チャレンジ

やはり敗因は、32 MiB と少なすぎる物理メモリと USB 1.1 という前時代の物理層しか搭載しないことだった。メモリがスワップした瞬間、Fallout シリーズの端末みたいなスピードでしか動作できなくなるし。

次なるルーターは 多摩電子工業 (Axing) W06 だ。

f:id:puhitaku:20180218001123j:plain

秋葉原はジャンク通りに面するショップインバースなどで見かけた人も多いだろう。一時期1000円を余裕で下回る価格で売られていた、持ち運びできるルーターだ。

f:id:puhitaku:20180218002405j:plain

  • CPU: MediaTek MT7688AN@575MHz
  • RAM: Winbond W9751G6KB@DDR2, 64MB
  • Flash: 16MB
  • Ether: FastEther (100Mbps) x1
  • USB 2.0 PHY 搭載・物理ポート付き

(出典: 鉄PCブログ

このルーターは価格に対してスペックが良い。CPU 575MHz。DDR2。USBポート付き。しかもUSB 2.0。WHR-HP-GN から四まわりほどアップグレードした感じだ。

こいつのファームをビルドし、ここまでとほぼ同じ作業をもう一度繰り返した。差分は以下。

  • SDカードの rootfs パーティションを ext4 ではなく btrfs にした
    • Kernel Modules -> Filesystem -> kmod-fs-btrfs

どうあがいても USB の帯域幅は DRAM の帯域幅より狭い。btrfs は透過的に圧縮をかけることができるので、CPUが十分に高速であれば(いやそんなに速くないとは思うけど) USB の帯域を少しでも節約できるのではないかと考え選択した。kmod-fs-btrfs を選択すれば、圧縮アルゴリズム系のモジュールも自動で選択してくれるようだ。

  • これまでに不足していた ca-certificates や grep などを追加した

そもそも WHR-HP-GN のような Flash が 8 MiB のシステムでも ppp や Wi-Fi 関連の config を消せばいろいろ入った。こちらはFlash が 16 MiB もあるので余裕で追加できる。

準備

ファームをビルドして実機に転送して SD をフォーマットして extroot を仕込んで ncurses をビルドして Python を落としてくる。

blkid してみると。

root@OpenWrt:~# blkid
/dev/mtdblock5: TYPE="squashfs"
/dev/sda1: UUID="e98d0f04-9cf6-447c-bee3-0c3d5ccb0aef" UUID_SUB="e8a7a945-1721-4fcf-99b4-669e49bea786" TYPE="btrfs" PARTUUID="3859753d-01"
/dev/sda2: UUID="765a8ea4-ef55-48fe-95c7-8c0fee8e9c0e" TYPE="swap" PARTUUID="3859753d-02"

おー、ちっこいルーターなのに btrfs やら swap やら盛りだくさん!ウキウキな瞬間だ。

mount。

root@OpenWrt:~# mount -o ssd,noatime,discard,compress=lzo /dev/sda1 /mnt
root@OpenWrt:~# dmesg | tail -n 11
[  136.750060] BTRFS: device fsid e98d0f04-9cf6-447c-bee3-0c3d5ccb0aef devid 1 transid 5 /dev/sda1
[  136.771595] BTRFS info (device sda1): disk space caching is enabled
[  136.784117] BTRFS info (device sda1): has skinny extents
[  136.794659] BTRFS info (device sda1): flagging fs with big metadata feature
[  136.826883] BTRFS info (device sda1): checking UUID tree
[  168.921098] BTRFS info (device sda1): enabling ssd optimizations
[  168.933092] BTRFS info (device sda1): turning on discard
[  168.943652] BTRFS info (device sda1): setting 8 feature flag
[  168.954885] BTRFS info (device sda1): use lzo compression
[  168.965599] BTRFS info (device sda1): disk space caching is enabled
[  168.978028] BTRFS info (device sda1): has skinny extents

FOOO!!! さて、extrootしよう。マウントオプションの追加も忘れずに。

root@OpenWrt:~# cp -r /overlay/* /mnt/
root@OpenWrt:~# block detect > /etc/config/fstab
root@OpenWrt:~# vi /etc/config/fstab
root@OpenWrt:~# cat /etc/config/fstab
config 'global'
        option  anon_swap       '1'
        option  anon_mount      '0'
        option  auto_swap       '1'
        option  auto_mount      '1'
        option  delay_root      '5'
        option  check_fs        '0'

config 'mount'
        option  target  '/overlay'
        option  options 'ssd,noatime,discard,compress=lzo'
        option  uuid    'e98d0f04-9cf6-447c-bee3-0c3d5ccb0aef'
        option  enabled '1'

config 'swap'
        option  uuid    '765a8ea4-ef55-48fe-95c7-8c0fee8e9c0e'
        option  enabled '1'

root@OpenWrt:~# reboot

...

root@OpenWrt:~# df -h
Filesystem                Size      Used Available Use% Mounted on
/dev/root                 9.0M      9.0M         0 100% /rom
tmpfs                    29.3M     52.0K     29.3M   0% /tmp
/dev/sda1                13.7G     16.6M     11.7G   0% /overlay
overlayfs:/overlay       13.7G     16.6M     11.7G   0% /
tmpfs                   512.0K         0    512.0K   0% /dev

成功!

opkg が相変わらず空気を読まない

GCC だけはファームに内蔵できないので opkg で入れる。

root@OpenWrt:~/ncurses-6.1# opkg update

...

root@OpenWrt:~/ncurses-6.1# opkg install gcc

うーん時間がかかる。kworker が食ってるのはなんだ。

Mem: 51644K used, 8384K free, 0K shrd, 1240K buff, 2948K cached
CPU:   1% usr  98% sys   0% nic   0% idle   0% io   0% irq   0% sirq
Load average: 3.56 1.75 0.71 4/67 2100
  PID  PPID USER     STAT   VSZ %VSZ %CPU COMMAND
  119     2 root     IW       0   0%  51% [kworker/0:1]
 2100  1911 root     R     4368   7%  20% wget -q -O /tmp/opkg-olfGfA/binutils_2.27-1_mipsel_24kc.ipk http://downloads.openwrt.org/snapshots/packages/mipsel_24kc/base/binu

どうやら相変わらず opkg が tmpfs にパッケージを置こうとするせいでスラッシングしているらしい。tmp-dirを変える。

root@OpenWrt:~# mkdir tmp
root@OpenWrt:~# opkg update
...
root@OpenWrt:~# opkg install --tmp-dir /root/tmp gcc

うん、目に見えて速くなった。

ncurses リベンジ

落として configure 。

root@OpenWrt:~# wget https://ftp.gnu.org/pub/gnu/ncurses/ncurses-6.1.tar.gz
Downloading 'https://ftp.gnu.org/pub/gnu/ncurses/ncurses-6.1.tar.gz'
Connecting to 208.118.235.20:443
Writing to 'ncurses-6.1.tar.gz'
ncurses-6.1.tar.gz   100% |*******************************|  3286k  0:00:00 ETA
Download completed (3365395 bytes)
root@OpenWrt:~# tar zxf ncurses-6.1.tar.gz
root@OpenWrt:~# cd ncurses-6.1/
root@OpenWrt:~# time ./configure

...

real    2m 13.33s
user    1m 26.98s
sys     0m 19.78s

かーなりマシになった。前は configure だけで40分かかっていたことを考えれば上出来だ。特にスラッシングも起きていないようだ。

make。

root@OpenWrt:~/ncurses-6.1# time make
...
make[1]: Leaving directory '/root/ncurses-6.1/c++'
real    30m 6.52s
user    23m 35.15s
sys     1m 32.85s

うーん速い!WHR-HP-GN が ncurses で configure するよりも速い。

Python リベンジ

落として configure。

root@OpenWrt:~# wget https://www.python.org/ftp/python/3.7.1/Python-3.7.1.tgz
...
root@OpenWrt:~# tar zxf Python-3.7.1.tgz
root@OpenWrt:~# cd Python-3.7.1
root@OpenWrt:~# time ./configure
...
real    4m 21.46s
user    3m 29.45s
sys     0m 42.52s

いい感じ。お次は makeなのだが、IO が発生するたびに kworker が相変わらず重い… gcc と CPU 取り合ってる…ということで btrfs の醍醐味 compress=lzo はお蔵入りに。残念。

root@OpenWrt:~# time make

kworker は幾分か軽くなったのだが、大きいオブジェクトファイルをコンパイルする時はスワップが発生して途端に遅くなるようだ。とりあえず、開始2〜3時間で WHR-HP-GNが止まっていた部分は追い抜いた。

ここで寝落ちし朝を迎えた時点で typeobject.c をコンパイルしていた。そのまま出勤。家に帰ると…

gcc -c -Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall    -std=c99 -Wextra -Wno-unused-result -Wno-unused-parameter -Wno-missing-field-initializers -Werror=implicit-function-declaration   -I. -I./Include    -DPy_BUILD_CORE -o Objects/tupleobject.o Objects/tupleobject.c
gcc -c -Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall    -std=c99 -Wextra -Wno-unused-result -Wno-unused-parameter -Wno-missing-field-initializers -Werror=implicit-function-declaration   -I. -I./Include    -DPy_BUILD_CORE -o Objects/typeobject.o Objects/typeobject.c

いやおめー全然進んどらんが!

no-unused-parameter -Wno-missing-field-initializers -Werror=implicit-function-declaration   -I. -I./Include    -DPy_BUILD_CORE -o Objects/typeobject.o Objects/typeobject.c

^C^C^Cmake: *** [Makefile:1623: Objects/typeobject.o] Interrupt
Command terminated by signal 2
real    17h 51m 36s
user    11m 55.93s
sys     5m 6.26s

途中まで進んで結局完全にスラッシングしちゃったのでした。フルな Python のビルドは無理でした。完。

Python 0.9.1 をコンパイルしてお茶を濁す

Python の開発の歴史は1980年代の末まで遡る。(突然始まる Python の歴史)もともとは Guido van Rossum のクリスマスの趣味プログラミングとして始まった Python は、1991年にバージョン 0.9.1 として世界に公開された。

実は、このバージョン 0.9.1 は今でもダウンロードし、コンパイルすることができる。

www.python.org

フルの Python はコンパイルできないので、こいつをコンパイルしてお茶を濁すことにする。

落としてコンパイルする。

root@OpenWrt:~# wget https://www.python.org/ftp/python/src/Python-0.9.1.tar.gz
Downloading 'https://www.python.org/ftp/python/src/Python-0.9.1.tar.gz'
Connecting to 151.101.0.223:443
Writing to 'Python-0.9.1.tar.gz'
Python-0.9.1.tar.gz  100% |*******************************|   378k  0:00:00 ETA
Download completed (387150 bytes)
root@OpenWrt:~# tar zxf Python-0.9.1.tar.gz
root@OpenWrt:~# cd python-0.9.1/src/
root@OpenWrt:~/python-0.9.1/src# CC=gcc time make

...

real    0m 37.69s
user    0m 33.26s
sys     0m 2.67s

あっはっは、37秒ちょっとで終わってしまった。やはり時代の古いプログラムはコンパイルも軽い。最初からこうすればよかった感すらある。

Python 0.9.1 をちょっと触る

※現代の Python との主な違いについては、同梱されている README.reconstructed にも書いてある。

print はある。

>>> print 'hello world!'
hello world!

dir() の使用できる範囲が限られている。現在では任意の object の attribute を一覧にできるが、0.9.1 では module の中の関数を一覧化する程度の機能しかないようだ。

>>> i = 1
>>> dir(i)
Unhandled exception: type error: dir() argument, must be module or absent
Stack backtrace (innermost last):
  File "<stdin>", line 1
>>>
>>> import math
>>> dir(math)
['acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'cosh', 'e', 'exp', 'fabs', 'floor', 'log', 'log10', 'pi', 'pow', 'sin', 'sinh', 'sqrt', 'tan', 'tanh']

class の定義ではクラス名の次に必ずカッコを付ける必要がある。

>>> class Foo:
Parsing error: file <stdin>, line 1:
class Foo:
          ^
Unhandled exception: run-time error: syntax error

__init__ という概念がない。標準ライブラリではコンストラクタとしてクラスと同名の関数を作り、手で呼んでいたようだ。

実際、__init__ を作っても呼ばれないことがわかる。

>>> class Foo():
...     def __init__(self):
...         print 'hello!'
...
>>> f = Foo()
>>>

ダブルクォートが文字列リテラルとして解釈されない。

>>> print 'Hello World!'
Hello World!
>>> print "Hello World!"
Parsing error: file <stdin>, line 1:
print "Hello World!"
       ^
Unhandled exception: run-time error: syntax error

次回

次回は、ルーターに備わっているペリフェラルにいろいろつないでみる企画『ルーターにつなぐ』の第1弾とする。まずは I2C にする予定だ。乞うご期待。