『ルーターにつなぐ』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日だけど!!!!)