2021年12月12日日曜日

GPU

仕事の都合上で触ることはあれど、それにあんまりディープに関わることは無いかな〜と思っていたものの、やはり画像を扱う「ブツ」を取り扱っている以上、そこから逃げているわけにもいかず…。

ということで、仕事で扱っているメーカーが、画像認識用のリファレンスデザインをできれば販売したいと考えているとのことで、そのリファレンスデザイン(以下単に「アプリ」)のセットアップを行う必要が出ました。

いっちばん最初はもう2年くらい前で、その時は、メーカー自身がセットアップしたラップトップPC(正確には、ゲーミングノートPC、と言うカテゴリ)を使っていたのだけども、「アプリの次世代ができてるんで、そっちでセットアップしてデモに使ってね」、とメーカーは言う…。


ラップトップPC

 ubuntuにCUDAその他認識で使用するツール一式、何分2年以上前のセットアップなので、ubuntu自体xenialだしcudaも8なので、そこからupdate必要、ということで、まずubuntuはbionicにupgradeで問題なく終わった!と思って再起動してその先に進めていると、何かのはずみにoopsを起こす。
おや???と思っているうちに立ち上がらなくなってしまいました。

このoops、単にプロセスがクラッシュしている旨のメッセージでしかなく、再起動しているうちに、起動できるときとできないときに遭遇することに。
起動できればoops起こさない限り使えてるし、これって、なんかのはずみにsleep状態から抜け出せなくなってんでない?と思ったのが問題解決から遠のく運の尽き。

3週間ほど、もしかしてストレージ(SSD)が壊れた?壊れてない?と、M.2 SSDを着脱、2.5"HDDいれてみたりメモリ全部抜き差しするなどの試行錯誤。

そして、最後の最後に突き止めたのは、8G×2 SODIMMのうち一枚がお亡くなり(なりかけている)、ということ。

メモリって、2枚組ではいっていると、あんまり、一枚ずつさしてみてどうか?って、見ないって事無いですか?

そいつを外してようやく進捗…。
なお、このラップトップにはGTX1080が搭載されています。

CUDA

 現時点で最新のCUDAは11.5だと思いますが、このアプリが要求するのは10.2。これは逆に、他に、そのソフトウエアセットアップを検討しているプラットホームの観点ではbetterな話だと思ったので、10.2インストールに勤しむことに。
しかし、このアプリでは、微妙に古いnvidia-driverが推奨となっており、CUDA vs. nvidia-driverのセットアップに悩むことに。推奨バージョンのnvidia-driverはパッケージとして存在しておらず。もしかして、CUDAはソースからbuildするの???

デスクトップPC

 デモセットアップって、常に私が使えるわけではないので、何か聞かれたときに、同一のアプリをインストールしている類似環境が存在していないと質問に答えられなくなってしまう。
そのため、社内で余剰になっていたデスクトップPCを一台譲り受ける。
当然、そんな業務用のデスクトップにはチップセット内蔵GPUが使われていて、nVidiaのGPUなんて搭載されてない。
ということで、GPU調達をすることになるわけですが、当然「お高い」ので、可能な限りローコスト、とにかく動かせること、レベルをターゲットにして、まずGT1030で、何が起こるかお試しすることに。

GT1030

 こいつはCUDAサポートのリストには無い、ということは知っててあえて試した状況。GDDRメモリバス幅が狭いので、そもそもパフォーマンスは期待できない、とは思ってました。
で。
このボード調達前に、デスクトップにはbionicのセットアップは終わらせてあったので、そこにこのボードを追加、CUDAインストールから試すんですが、この時点で、「あること」を知らずにおり、アプリ推奨に近い版のnvidia-driverインストール後再起動すると、GUIが立ち上がらない、persistencedが動作しない、結果としてCUDAを利用できない、スケジュールも厳しい、という焦りに負けて、3日でgive up、CUDAサポートリストにあるGPUを調達することに。

GTX1650

 土曜に秋葉原に出かけ、GTX1050tiとGTX1650、どちらを選ぶか、1050tiの方が安いなー、ラップトップは1080だから同じ世代だしなー、ではあったんですが、設計古いことから消費電力とコア数で、GTX1650を選定。
「あること」をちゃんとわかってなかったのだけど、週明けにnvidia-driverの比較的新しい版をインストールした状態で、GT1030を試していた時点で残してあったCUDA10.2の組み合わせで、今から思えば不幸にも動いてしまったため、その先に進めることに。

いよいよアプリインストール

 このアプリは2つのアプリで構成されていて、一つ目のアプリが動作した上で、二つ目のアプリをセットアップすることができる。
一つ目のアプリでは、GTX1650で動いてしまった関係で、ラップトップに戻ってセットアップ…すると、動かねー。
ラップトップとデスクトップのセットアップの違いは、GPUの違いに基づくnvidia-driverの違い。つまり、そこで何か間違えたことをやってる、ということ…。

「あること」

 それって、GPUガンガン回すゲームとかAIとかやってる人にはおそらく常識、「nouveauは使わない」、です…。それでラップトップを設定し直し、二つのアプリ、両方とも、デモ可能な状態にできました、まだ調整は必要だけど。
デスクトップの方は、CUDAを使用する他の利用目的もあることと、パッケージをマニュアルで足したり消したり様々試したため、再インストールの予定です。


行き先のなくなったGT1030は…

 過剰になった部品を経費で落とすわけにはいかないこともあり、中古にだすのも気が引けて、引き取ることにしました。
で。以上、確立したインストール手法をもとに今現在、E450でCUDAサンプルをbuild中。動かせるじゃーん、ということで、しかし一方、元々CPUが非力すぎるため恩恵もあまりなく、もしかすると、AI関係のスタディにもっとも寄与してくれる存在になるかもしれません。
Jetson nano、結構安いんで一つ買っても良いかなーとは思ってたんですが、GT1030の方がGPU世代が新しいこともあり(nanoは5.3、1030は6.1)、まー、それもありかな〜、と言うところです。

…、それにしてもGPU高い。ラップトップの価格を考えると、グラボとしてGPU調達するより、ゲーミングノートを買った方が多分効率よいよね…。

2021年9月30日木曜日

BL190HW、いい加減にしてくれ…

 最近、突然宅内ネットから『外』が見えなくなる。在宅勤務中に起こるととっても困る…。

なんでなのか結構しばらく悩んでたんだけど、BL190HWを再起動すると正常になる。で、設定をざっと確認してみると、異常時IPv4 DNSサーバー欄が空欄になっている。こいつが問題。

再起動から最短数時間は問題ない一方で、こいつはログをとれないことから、外側のDHCPサーバーからアドレス更新されたから、とか、そういう確認は全くできない。何せ突然DNSサーバー欄が空白になる。

auのサービスに連絡したところで、お決まりの、『WAN側サーバーに異常ログは無い』『電源入れ直せ』『初期化しろ』、挙句には『見に行く』『交換する』というお決まりのルートになる(今回はとりあえず『初期化しろ』で踏みとどまった)。auは原因究明するつもりがあるのかどうか? そんなことを電話で言ったところで、電話受けてるコールセンターさんが困るだけでらちが開かない。

これ、うちだけなら確かに個体の問題かもしれないけど、どうやらそうではない。『BL190HW DNS 空欄』あたりで調べると、やはり他にもお困りの方がいる。

とりあえず、あんまり引っかかってほしくない一台だけ8.8.8.8を見てもらって、その他はdnscacheを立てて、そちらを参照するようdhcpを変更しようと思う…。dnscacheなんて、だいぶ長いこと使ってないや。

2021年9月9日木曜日

GY-521

  春頃に使ったGY-521、amazonでまとめて5個買いだったこともありまだ残っています。ちょっと思いついたコトがあって、新しいものを開封して、試験的に動かしてみました。

前回は、あんまり深く考えずにi2cをそのままパタパタさせてたんですが、今回はその目的のライブラリを使うに際しi2cまわりも一緒に存在しているので、ライブラリ付属のコードをそのまま頂いて動かして見ようとしてたんですが、うんともすんとも言わない。

元のコードが例によってmbed2用なので、、mbed6用に書き換えたりしてはいますが、基本線は変わらないわけで、調べてみるとwhoamiで引っかかってる。デバイスの応答は、0x68じゃなくて0x98。こんな中途半端なビット化け?と思って調べて見つつ、改めてしげしげモジュール眺めてみると、デバイスにはMPU6050Aと刻印されている。ふーんと思ってinvensense/TDK調べてみるとそんなデバイスは出てない様子。

参照:https://forum.arduino.cc/t/mpu-6050-a-module-problems-who-am-i-reports-0x98-not-0x68-as-it-should-fake-mpu-6050/861956

Amazonにある写真では、MPU6050になってるんだけどね…。

春に使ったときも、whoami最初は見たような記憶があるんだけど、あんまり気にせずだったと思います。今回はこれに磁気センサを追加してAHRSとして動かすことも考えてたんですけどね…。はてさて。

2021年7月31日土曜日

Time server

 半分仕事でやったことなんでここには書いてなかったんですが、これをやっていたとき、並行して、GPSから出力されるppsを使ったタイムサーバーを、NanoPi NEOを使って仕立ててました。

さすが外部のntpサーバーを見ているより高い精度の時刻が得られているようですが、何分バラックセットで運用しているので、ケーブル引っ掛けたり何だりしてしまう関係で、箱に入れることを考えつつ、ただ箱が置かれると、家族的には『なにこれ?』となることを請け合いなので、何か表示でもしておこう、というところに取りかかることにしました。

LCDつなげる

実際、ntpサーバーって地味ですよね…。ただ動いていることを示しておきたいだけなので、今まで、手元にはありつつ、Arduinoで使おうかなーと思いつつ調達したものの、眠っていたキャラクタLCDを使ってみることにしました。たしか、こちらだと思いますが、バックライト用のピン位置が標準とは違うものです。

4ビットモードで使えるだろうとはいえ、Linuxサイドからなるべく簡単にコトませたいので、アマゾン頼みI2C I/Fを調達し手搭載(このLCDでは、バックライト端子のみ別配線する工夫が必要ですが…)、NanoPiへの配線も4線で。Osoyoさんのサンプルコードを使って、表示確認まで済ませました。

何を表示するのか?

キャラクタLCD、16文字2桁しかないし、つけっぱなしで寿命どのくらいなんかなーと言うことが若干気になりつつもまぁ5年くらいは動くのかな…。

で、表示内容は、こいつがなんなのか(つまり"Time Server")、GPSから得られるその日付時刻(UTCですが…)、測位モードにエアの状態、衛星は見えてるかいくつ? くらい、さすればこのntpは今GPS使えてるか自走モードかくらいは判別できるので、その辺繰り返し表示しておけばよいかなー。

どやって表示するか?

ntpサーバーはgpsd使っているし、表示テストに使ったサンプルコードはpythonだしそれなら後で表示内容簡単に変えられるし、そこにgps情報を付け加えればえっかなと、ざっくり。"python gpsd"でググるとgps3というpythonパッケージが目に止まって動かしてみる。何々?十分な情報とれそうだ、でもそんなに色々情報使いはしないけど、と思って使ってみると、測位に使っている衛星個数情報を取り出せない。ま、測位モードだけ判れば良いかとおもい、その表示を行わせてみると、時間更新がうまくいかないし、どうもgpsdから与えられる情報にゴミが含まれることがあるのか、クラッシュする(pythonプログラムが落ちるわけではないが止まってしまう)。使っているGPSその物に依存する可能性はあるけど、これじゃちょっと辛い…。

改めて検索しこれを試すと、シンプルだし時刻データもちゃんと更新される。osoyoのコードそれを付け加えたのが次のコード。gps3が問題なく動くなら、satellitesも同じようにリストになってると思うので、それ調べて測位に使われているならカウントしてやれば良いだろうと思う。


#
# Project Tutorial Url:http://osoyoo.com/?p=1031
#
# GPSd related codes: class GpsPoller() etc.
# Written by Dan Mandle http://dan.mandle.me September 2012
# License: GPL 2.0
#  
import smbus
import time
import threading
from gps import *
gpsd = None
class GpsPoller(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        global gpsd
        gpsd = gps(mode=WATCH_ENABLE)
        self.current_value = None
        self.running = True

    def run(self):
        global gpsd
        while gpsp.running:
            gpsd.next()
# Define some device parameters
I2C_ADDR  = 0x27 # I2C device address, if any error, change this address to 0x3f
LCD_WIDTH = 16   # Maximum characters per line
# Define some device constants
LCD_CHR = 1 # Mode - Sending data
LCD_CMD = 0 # Mode - Sending command
LCD_LINE_1 = 0x80 # LCD RAM address for the 1st line
LCD_LINE_2 = 0xC0 # LCD RAM address for the 2nd line
LCD_LINE_3 = 0x94 # LCD RAM address for the 3rd line
LCD_LINE_4 = 0xD4 # LCD RAM address for the 4th line

LCD_BACKLIGHT  = 0x08  # On
#LCD_BACKLIGHT = 0x00  # Off
ENABLE = 0b00000100 # Enable bit
# Timing constants
E_PULSE = 0.0005
E_DELAY = 0.0005
#Open I2C interface
bus = smbus.SMBus(0)  # NanoPi NEO / Rev 1 Pi uses 0
#bus = smbus.SMBus(1) # Rev 2 Pi uses 1

def lcd_init():
  # Initialise display
  lcd_byte(0x33,LCD_CMD) # 110011 Initialise
  lcd_byte(0x32,LCD_CMD) # 110010 Initialise
  lcd_byte(0x06,LCD_CMD) # 000110 Cursor move direction
  lcd_byte(0x0C,LCD_CMD) # 001100 Display On,Cursor Off, Blink Off 
  lcd_byte(0x28,LCD_CMD) # 101000 Data length, number of lines, font size
  lcd_byte(0x01,LCD_CMD) # 000001 Clear display
  time.sleep(E_DELAY)

def lcd_byte(bits, mode):
  # Send byte to data pins
  # bits = the data
  # mode = 1 for data
  #        0 for command

  bits_high = mode | (bits & 0xF0) | LCD_BACKLIGHT
  bits_low = mode | ((bits<<4) & 0xF0) | LCD_BACKLIGHT
  # High bits
  bus.write_byte(I2C_ADDR, bits_high)
  lcd_toggle_enable(bits_high)

  # Low bits
  bus.write_byte(I2C_ADDR, bits_low)
  lcd_toggle_enable(bits_low)

def lcd_toggle_enable(bits):
  # Toggle enable
  time.sleep(E_DELAY)
  bus.write_byte(I2C_ADDR, (bits | ENABLE))
  time.sleep(E_PULSE)
  bus.write_byte(I2C_ADDR,(bits & ~ENABLE))
  time.sleep(E_DELAY)
def lcd_string(message,line):
  # Send string to display

  message = message.ljust(LCD_WIDTH," ")

  lcd_byte(line, LCD_CMD)

  for i in range(LCD_WIDTH):
    lcd_byte(ord(message[i]),LCD_CHR)

def main():
  # Main program block

  # Initialise display
  lcd_init()

  gpsp.start()

  while True:
    gps_satellites = gpsd.satellites
    num_sats_available = 0
    for item in gps_satellites:
        satdata = str(item)
        num_sats_available = num_sats_available + satdata.count("Used: y")
    lcd_string("GPS time server ",LCD_LINE_1)
    gps_mode = gpsd.fix.mode
    if gps_mode == 1:
        mode_string = "SATs not fixed"
    elif gps_mode == 2:
        mode_string = "2D mode, "+str(num_sats_available)+"SATs"
    elif gps_mode == 3:
        mode_string = "3D mode, "+str(num_sats_available)+"SATs"
    else:
        mode_stroing = "Unknown"
    lcd_string(mode_string, LCD_LINE_2)

    time.sleep(2)
  
    # next page display -- time
    gps_time = gpsd.utc
    dt_today = gps_time[:10]
    dt_time = gps_time[11:20]+"(UTC)"

    lcd_string(dt_today,LCD_LINE_1)
    lcd_string(dt_time,LCD_LINE_2)
 
    # next page display -- time
    gps_time = gpsd.utc
    dt_today = gps_time[:10]
    dt_time = gps_time[11:20]+"(UTC)"

    lcd_string(dt_today,LCD_LINE_1)
    lcd_string(dt_time,LCD_LINE_2)

    time.sleep(3)


if __name__ == '__main__':
  gpsp = GpsPoller()
  try:
    main()
  except (KeyboardInterrupt, SystemExit):
      print ("\nKilling Thread...")
      gpsp.running = False
      gpsp.join()
  finally:
     lcd_byte(0x01, LCD_CMD)


以上、テストラン中。後は箱作りですが、それはまた今度。最悪、LCDバックライト寿命については、ジャンパでon/offできるので、トグルスイッチでもつけるかな…。

2021年6月14日月曜日

Lichee Zero Dock その1

 カメラ、というか動画をいじろう、ということを前から思ってて、カメラモジュールも持ってるものの、なかなか手をつけられずにおりました。手元のボードコンピュータたち、カメラI/F、いわゆるCSIがあるのは、ラズパイ3Bと、PineA64なんですが、ラズパイは言わずと知れた「指定センサ以外はまず使えない」、A64は別の仕事をしている関係で容易に使えず。ということで、何かないかなー、A64は8bitパラレルCSIはOKだけどMIPIは使えないなーと思ってた所に、次の2つを見つけました。

別段新登場、ということではなく、今までそういう目で探してなかった、ということなんですが、一つはM1n、もう一つはLichee Zero Dock、どちらもSipeed製。2枚合わせてもラズパイ買うより安い…(いや比べる事自体正しくない)。で、M1nは、Linuxはちょっと難しそうでRTOS必要そう、かつ、目的がちょっと違うので、一旦置いておいて、Licheeからいじってみることにしました。

 

Lichee Zero Dock

 Lichee Pi Zero? Lichee Zero? どっちでも良いみたいですが、それをコアにして、すこーし周辺I/Fコネクタを追加したもの、と言って良いかと思います。ただ、Zeroはブレボに刺して使うイメージですが、Dockはさらにコネクタなどが乗っている都合で、ブレボに刺してもあんまり嬉しい状況にはなりません。Ethernetを使えてほしかった関係で、ZeroではなくZero Dockを調達してます。そういうわけで、最低限必要そうなUARTだけとりあえず引きだすためのボードを用意しました。

電源も、UARTのところから引けるんですが、母艦側がちょっと苦しいので、5V電源を追加してあります(UART側は当然外します)。以下では省いてるんですが、最初、ちょっとLCDをつないでみたときは、LCDバックライト依存性あるとはいえ電源が窮屈だったようでした。

 

OSと初期設定

Sipeedのサイト から mindb_dd.tar.gzをダウンロード、tar xfして出てくるlichee_zero-mindb_alpha.ddをmicroSDにdd。起動してみると、このddイメージはDebian Jessieです。
 

この段階ではethernetは生きていません。でもネットにはつなぎたいので、Wi-Fiどうかなと思いTL-WN725NをUSBにつなぐと、r8188euで認識してくれました。
『最小パッケージ』なので、NetworkManagerとかいないので、interfacesとwpa_suppliant.confを作成してまずは動かします。
 wpa_supplicant.confのスケルトンはあるので、wpa_passphraseでssidとパスコードからpskを作らせます。
 /etc/network/interfacesにwlan0を追加し、wpa-confがwpa_supplicant.confを示すようにしてやります。これでネットワークにつながりました。

 armbianみたいにパーティション自動最大化とかはしてくれないので、マニュアルでパーティションサイズ変更してresize2fs実施します。
    fdisk /dev/mmcblk0
    /用パーティション一旦削除して、改め最大値で確保します。
    write→quit→reboot(partprobeとかないので)
    立ち上がり後resize2fs -f /dev/mmcblk0p2

Lichee Zeroには、内蔵メモリ64Mしかないのでswap足しておきます(128M追加)。
    dd if=/dev/zero of=<filename> bs=1M count=128
    swapon
  /etc/fstabに追加して再起動時でも有効にします。

さすがにDebian JessieはLTS的にも切れてるので、せめてstretchにupgrade。カーネルは4.10.2のまま。stretchにupgradeするときには、debian-keyringのupdateが必要なことを忘れがち、ということと、多分、Wi-Fiがfirmware必要なので、追加しておきます(大丈夫だったかもしれない)。しかし、ethernetが使えないのは痛い…。


ethernet


 サイズ的な大小はあるにせよ、こういう「規模」が当たり前だった昔ならいざ知らず、いまだとこのクラスのマシン用イメージはセルフではなくクロスビルドが普通だよな…と思いつつ、こちらのサイトに、ethernetを有効とする手順紹介があるので、それを元にセルフビルドしてやります。中国語は読めないので、Web翻訳使いながら読みます。
u-bootはdenxではなく同じサイトにリンクがあるこちらこちらを使います。


makeできたら次で書き込む。
 dd if=u-boot-sunxi-with-spl.bin of=/dev/sdX bs=1024 seek=8

u-boot直しただけではu-bootレベルで触れる、と言うだけなので、Linux側も、ドライバ、デバイスツリーを修正します。そのため、カーネルソース、次を利用
 
時間計らなかったけど半日は掛からなかったようです。
make install modules_installではなく、dtbとzImageを/bootにcpしてreboot → お、LEDが点灯& ethernet稼働!

 

カーネルをより新しく…


 これで4.12.0-rc1。しかし、それにしてもこのカーネルはちょいと古く、linux-sunxiサイト見ても、主目的であるカメラサポートは5.0以降となっている。そのため、このイメージはここでストップして、改めて5.10のイメージ作成にチャレンジする。
最新u-bootにすべきかもしれないけど、device treeが結構違っているようで、マージには相当手間が掛かりそうなので一旦捨て置いて、Linuxカーネルソース5.10.42をcloneして、sunxi_defconfigしてbuild、ほぼ5時間で終了。
/bootにdts、zImageをcpして、make modules_install、rebootで5.10.42は起動しました。
なお、カーネルdevice treeには、u-boot同様の変更が必要です(Linuxサイドが新しいので、一部、語を変更する必要がある。)

次の問題


 だが、USBが動いていない模様で、u-bootで有効にできていないようであることが判明。u-bootを使った確認方法


U-Boot 2017.01-rc2-00057-g32ab1804cd-dirty (Jun 13 2021 - 15:14:29 +0900) Allwinner Technology

CPU:   Allwinner V3s (SUN8I 1681)
Model: Lichee Pi Zero
DRAM:  64 MiB
MMC:   SUNXI SD/MMC: 0
*** Warning - bad CRC, using default environment

Setting up a 480x272 lcd console (overscan 0x0)
dotclock: 10000kHz = 10000kHz: (1 * 3MHz * 20) / 6
In:    serial@01c28000
Out:   serial@01c28000
Err:   serial@01c28000
Net:   phy interface0
eth0: ethernet@1c30000
starting USB...
USB0:   musb_init_controller failed with status -22
probe failed, error -5
USB error: all controllers failed lowlevel init
Hit any key to stop autoboot:  0
=>

ということなので、ここのレベルでUSBを有効にできていない。mindbにあるu-bootではちゃんと使えているのだが、.configがなく…。

結構、config様々試してみたんですが動いてくれず。素人目なので確実ではないけども、u-bootのUSBまわりを追ってみると、V3sのサポートが足らないような気がする。Ethernetについても、最新のu-bootでもまだのようなので(patchが流れていた)、V3s自体、まだ十分にサポートされてはいないようです。

がまぁ、ネットワークは動いていることだし、カメラをつなぐことを考えてみたいと思います。

とりあえず今回ここまで。

2021年3月28日日曜日

BL190HWとArcher C6

 …、どうもC6経由のdhcp割り当てがうまくない。有線clientがいるのでBL190HWにdhcpやらせてC6はブリッジモード運用なんだが、時にC6経由で中継されるリクエストがきっかけなのか、BL190がアドレスを振り出さなくなってしまう。

こうなるとBL190の再起動が必要。日に数回発生し得るので、ファイルサーバーにもdhcpサーバーをやらせて、しばらく割り当て状況を監視、だな。

2021年3月12日金曜日

Nucleo64+ArduinoLCD+MPU6050の明日はどっちだ? (承前。GPS追加搭載。)

前回、加速度センサつなげて温度計つなげて、って所までで止めてて、GPSもつなぎたいな…というところで終わりにしていたんですが、その時までにGPSを試していなかったのか、と言うと、そういうわけではなく、GPS側の設定の問題と、シリアルの取扱い方が判らなくて、詰まってたんです。

手元にあるのはUART接続の古いGPSモジュールなので、UART経由でデータを読んでやる必要があります。

ArduinoにはTinyGPSPlusと言う優れものがあり、年末のArduino UNOではそれ使いました。mbedにもそのポーティングがあるんですが、先だって書いたとおり、それがmbedいくつ向けか、という微妙な点があります。それに纏わり引っかかった、mbedにおけるシリアルの取り扱いの変遷があります。

 リアルタイム性を含めてのことなのかなとも思いますが、シリアルやらでデータを交換する時に、C言語までの知識ではgetc()/putc()使う、って教わるように思うんですが(本職の皆さん違ってたらごめんなさい)、getc()もputc()も、mbed 2にはあれど、mbed 6には存在しないんです。
おまけに、mbedには、2014年頃まで、トラ技誌に付録でついたLPC11U35のボードのおかげか(そいつが前回記事で、なんとmbed 6はサポート外なんかい…となったきっかけなんですけど)、日本国内で流行った時期があった関係で、概ね2016年以前のmbed実績のblogなどは、mbed 2、ほぼそれで書かれており、getc()/putc()を使用、せいぜい、serialとrawserialという区分けまでで、はて、mbed 6はどうしたら…となっていたわけです。

Mbedサイトで、シリアルにはBufferedとUnbufferedがあって、getc()じゃなくてread()で取り込む…てのは読んで判ったわけですが、どちら使ってもOSが落ちhaltしちゃう。前回の時は、そこまで追求できなかったんです。
で、改め、unbufferedの時、何で落ちてるのか調べようと思って始めたのが今回なんですが、なぜかdebuggerを使うようbuildすると、もうdebuggerとの通信の時点でOSが落ちちゃう。OSが吐くログは、STLink絡みで問題があるようだけど、とてもそんな所までデバッグする能力はないので、bufferedでどこまで動くか見てみよう、ということで、取り込んだものをprintfさせてみると、どうもnon-printable charを拾っている感じ。これはもしかして、TinyGPSPlusが要求するNMEAを喋ってないんじゃないの?とようやく思い当たりました。


GPSモジュール自体は、NMEAを出力するよう設定して、バックアップバッテリを接続していたので、NMEAのままだと思い込んでいたんですが(古いので、それがないと、電源投入時に、NMEAではない初期状態の出力になっちゃう)、何かのはずみにバッテリへの結線が外れてたんです…。つまりGPSデフォルトで出力していました。それでTinyGPSPlusが解釈できていないことが原因だったと。

それを直し、改めてNMEAを出力するよう設定して走らせてみると…
右の写真ではまだ衛星補足していませんが、時間が取れていることから、ようやく、GPSが出力するデータを読めるようになったようです。衛星捕捉できれば、ちゃんとLAT・LNG表示してくれます。

写真では分かり辛いですが、Arduino LCDを載せたF411RE、SK-16に、クッション付き両面テープを置いて張り付け、その左がわちょうどUSBコネクタの下あたりにGY-521を、同じように両面テープで張り付け、手前側にGPSバックアップ用CR1220が張り付けてあります。 
GPSと、Arduino LCDに搭載されているmicroSDへの書き込み開始スイッチを蓋に張り付けてやって、なんとか収まるようにしました(LCD+F411REの背が微妙に高いのと、ちょいと配線量が多くて、ふたがちゃんとしまりきらないですが…)。

これでも、LM75Aによると、F411REとLCDの間は、LCDバックライトで温められてるとはいえども36℃ほどで収まっているようです。

それにしても、ここに持ってくるまでに、こんなに時間掛かるとは…(何分古いGPSなので、時計はrolloverしてます…)。mbedにはだいぶ悩まされました。
性質的には、GPSはunbufferedで動かす必要があるようには思うんですけど、OSクラッシュは、GPSがNMEA話しているかどうかには関係なく、私程度のソフトウエア知識では対応できましぇーん!


3/29追記:

 昨晩、なぜUnbufferedSerialだとクラッシュするのか、はっと気づきました。プログラムフロー的に非同期な扱いをする部分で、同期乗り換えを必要とすることやっちゃダメっすよね。
さっきそれ直して、UnbufferedSerialを使ってGPSを読み、エンコードし、加速度センサなどの出力と併せて表示できるようにできました。
これで、GPSの時刻データも毎秒表示されます(buffered serialで、バッファしたもの解釈させると、取りこぼしの上数分単位でしか更新できなかったんです…)。

サンプルコードがあるからって、それを鵜呑みにしといちゃダメすね…。特にBuffered Serial / Unbuffered Serialみたいなtiming issueがある実装に対しては。

2021年2月21日日曜日

Nucleo64+ArduinoLCD+MPU6050の明日はどっちだ?

 お仕事関係で移動データを記録する必要があり、年末年始にArduino UnoにMPU6050とu-bloxのGPSつなげて、USB経由でデータを垂れ流すってのやりました、と言うのをチラッと言ったかと思います。でも、作りましたったって、Unoコンパチ機を使い、ユニバーサルになってるシールドにお馴染みGY-521とGPS6MV2をのせて、ソフトを切り貼りしただけのものなんですが、こいつから出てくるデータを眺めていて、『6軸の加速度センサって、そういや初めて使うや』、つまり、出てくるデータの意味を判ってない、と。
データ分析のために、本体上にロギング必要よね、あとで見るために、ということなんですが、今までいじってきたLCDモジュールにはすべてSDカードソケットがついているとはいえ、記録開始などの仕組みとしてハード的なボタン?タッチパネル?など考えたりすると、 Arduinoの少ないピン数とCPUパワー、箱も含めると結構大変そう。

とにかく持ち運べないと意味ないので、ブレボのままとはいかず箱には入れないと。そうすると、Arduino用の8bitパラレルのLCDシールドを使うと、結構しっかりArduinoと噛んでくれるんで、あとはMPU6050関係と電源と…と少し負担は減りそうですが、LCDは使わないピンまで一緒になっている関係で、さらに周辺を加えるとなると…と、結構考えあぐねてしまう事になりました。

そこで、以前(数年前)、ちょいとしたコントローラに、STMのNucleo-64(F411RE)を使ったことを思い出し、あれならArduino感覚で… 実際にArduino IDEでも使えるんですが、mbedでも切り張り的コーディングで結構動かせたよな、手元に、mbedが宣伝され出した頃のLPC11U35なボードがあるのでまずはそれで…なんて思ったのが運の尽き。
途中で思い出したんですが、あんときゃArduinoIDEでもmbedでもなく、SystemWorkbenchとCubeをつかったんだった…。

 

LCDから始めよう

まぁLCDじゃなくても良かったんですが、Arduinoでもその他Linuxが動くSBCでも動かしている経緯があるので、そこで使っているモジュールなら、何となく勘どころは判ります。で、オンラインIDEを使い、UniGraphic(これはArduino用のライブラリと類似)を使って確認します。
UniGraphicにはテスト用のコードが存在してるんですが、そこでは、シリアルを使ってデバッグメッセージ出力します。コードをオンラインIDEでビルドすると、「は?wait_ms()? Serial()? mbed2はもう古いからとっととmbed5に移れや」的なことを言われます。はいぃぃ?

 

mbed "6" ?????

このあたりなんの前知識もなく着手してしまったわけですが、そもそもmbed5ってどうすんの?オンラインIDE使えないの?と右往左往し、結局、CLI使うかmbed Studioなのね、ということになります。で、x86が必要、と。作業環境はまずaarch64なので、ほぼそこで挫折しかかったんですが、まぁ、仕方ないか、と。

大して使わないけど時に必要になる、エントリクラスのWindows機に、外付けUSBでLinuxをインストール(いや別にWindowsにインストールしても良かったんですが、あんまりWindowsでコードいじりたい、という気持ちがなく…)し、mbed Studioインストール、mbedOSは6.7.0でした。

で、驚くことにLPC11U35もmbed5ではサポート外となっており使えない。えぇぇ?? そこで仕方なくF411RE。

LCDには、F411REがきたこともあり、SPIタイプのILI9341じゃなく、8bitパラレルのILI9341を使います、遅いんですが。でもこれを使うと、LCDをどう安定して接続するか、という問題から逃げやすく、microSDソケット、I2CにLM75Aがついていて他のテストもできるね、というところです。
抵抗式ですがタッチパネルがついている(SPIのものでもタッチパネル自体はついてるんだけど、コントローラが乗ってない)のですが、これを読むにはアナログポートが必要だということと、LCDの制御信号、データと共用されているため、どうやったら手を出せるか、それを使うなら、改めて考える必要がありそうですので棚上げしておきます。

 

そしてどつぼに…

まぁ、UniGraphicは良いんですよ。それとLM75A動かして温度見るまでは比較的問題なく、シールド側でI2Cはプルアップされているので、MPU6050いやGY-521のI2Cに入っているプルアップを外してやれば、あとはreadとwriteやればデータは読み出せるので。
LM75AにしてもMPU6050にしても、先人がライブラリを作ってくれているんですが、低レベルのi2cコマンドのwrapperを作る際、『お作法』がmbedでは共通化されていないのかそれともmbed2 - mbed5の呪縛なのか、まちまちな実装になっています。結果、複数のI2Cデバイスをハンドリングしようとすると逆に、i2cデバイスなのに引数が違ったり、wrapper対策が必要になったりして全体的な見通しが悪くなる問題があります。このため、I2Cデバイスはすべてread()/write()でインターフェースすることにしました。
ここまでで、LM75AからもGY-521からも、データを読み出しLCD上に表示するところまで、実装できました。UniGraphicなどにあったwait_ms()は単にすべて1000倍してwait_us()を使うようにしてあることもあるとは思いますが、少し重たい感じになってます。

問題はSDカード。

まず、シールド型のLCDのSDカードは、ICSPのSPIを使用していて、加えてCSが、Nucleo側でSPIに割り当てられないところにでています。Nucleo-64はUno互換コネクタがあるとはいえICSPなんてサポートしてないので、 ICSP部分に普通の2.54mmピッチピンヘッダを置こうとすると、Nucleo-64自身と干渉して、シールドを搭載できません。
このため、シールドを分解してICSP用の6pinピンソケットを外し改めて配線などするか、ICSPピンソケットからL字のピンヘッダ(モジュールに使用して、基板と平行にピンを引き出すためのもので、普通にL字ヘッダとして売られているものとは異なり、短辺側に、プラスチックの連結構造があるタイプが必要です)を使用してSPIを引っ張り出す必要があります。
シールドは、基板とLCDがクッション入りの両面テープでしっかり貼られており、これを分解しようとして以前LCDを破損したことがあるのでその方法はとらず、ピンヘッダを使う事にします。
また、LCDのArduinoコネクタから、SDcardのCSがいる部分のピンを引っこ抜いて、あらためケーブル設置し、SPI CSとして利用できるようにします。ついでにICSPにあるLCDリセットも、Nucleoのリセット端子につなげてやります。これでILI9341にも確実にリセットをかけられます。

 



 

ハードウエア的な作業は以上で、Nucleo側は、SPI2が使いやすい位置にあるのでそれ使いたいんですが、それだけでSPI2を使ってSDカード使えるのかと思ったらさにあらず、ここからが本番。

どうやってmbed5(以降、のつもり)でSDカード扱うのか探してみると、まずSDFileSystemが引っかかります。なになに?、これを使うには、mbed_add.jsonに宣言が必要?、はいはい、 あれ、でもそんなファイル無いと調べると、プロジェクトルートに新規に作成しろと。書き方もよく判んないけど、はいはい。
で、ライブラリをインポートしてみると、その時点でエラー起こしてるじゃん。はて?、と思うとこいつはもうmbed6では使えない、mbed6ではSDBlockDeviceを使え、それと、ボードのサポートリストはmbed5に比して大幅に圧縮してるんで、ハードウエア記述用のスクリプトを調べろ、と言われます。

で、SDBlockDevice.hをincludeさせようとすると、見つからない、と言われる。いやいや、あるじゃん…。これでひとしきり悩んだんですが、mbed_app.jsonで記述するボード名、これが問題で、Targetに指定しているものと同じ(NUCLEO-F411RE)かと思っていたら然にあらず、コンパイラで指定されているボード名?NUCLEO_F411RE、である必要があった、と。mbed_app.jsonに追加したのが有効になってなかったのね…。最終的にはこんな感じ。

-- mbed_app.json

 {
    "target_overrides": {
      "NUCLEO_F411RE": {
          "target.features_add" : ["STORAGE"],
          "target.components_add" : ["SD"],
          "sd.SPI_MOSI" : "PB_15",
          "sd.SPI_MISO" : "PB_14",
          "sd.SPI_CLK"  : "PB_13",
          "sd.SPI_CS"   : "PB_12"
      }
    }
}

 

このあたりを直して、出来上がったのが次のコード。GPSのデータも記録したいところなんですが、まずは6軸センサのデータをSDカードに書けるようになったので、最初の目的は果たせそうです。  


--- main.cpp
#include "PinNames.h"
#include "stdio.h"
#include "mbed.h"
#include "BufferedSerial.h"
#include "string"
#include "Arial12x12.h"
#include "Arial24x23.h"
#include "ILI9341.h"
#include "BlockDevice.h"
#include "SDBlockDevice.h"
#include "FATFileSystem.h"
#include <cstdio>

static BufferedSerial pc(USBTX, USBRX, 115200); // for debug console

DigitalIn trig(PB_2);   // push button sw

// For ILI9341 Arduino Shield
// power-on reset does not work well, has to connect LCD_reset to reset pin.
PinName myport[8] = {PA_9, PC_7, PB_6, PA_7, PB_5, PA_5, PB_10, PA_8};
ILI9341 myLCD(BUS_8, myport, PB_0, PH_1, PA_4, PA_1, PA_0,"myLCD");

I2C LM75A(I2C_SDA, I2C_SCL);
I2C MPU6050(I2C_SDA, I2C_SCL);

#define LM75A_addr (0x48 << 1)     //0b100100_0
#define MPU6050_addr (0x68 << 1)   //0b110100_0

char i2c_cmd[2];
char i2c_dat[14];    // first byte is address, max 14 bytes read data

int16_t ax, ay, az; // Accelerometer x/y/z
int16_t gx, gy, gz; // Gyroscope x/y/z
int16_t tp;         // Temp sensor on the module

// SDcard for data storage
//SDBlockDevice bd(MBED_CONF_SD_SPI_MOSI, MBED_CONF_SD_SPI_MISO, MBED_CONF_SD_SPI_CLK, MBED_CONF_SD_SPI_CS);
BlockDevice *bd = BlockDevice::get_default_instance(); // That's all right..
FATFileSystem fs("fs");

unsigned short backgroundcolor=Black;
unsigned short foregroundcolor=White;

char orient=3;
unsigned int record = 0;


int main()
{
    myLCD.set_orientation(orient);
    myLCD.background(backgroundcolor);    // set background to black
    myLCD.foreground(foregroundcolor);    // set chars to white
    myLCD.cls();                          // clear screen
    myLCD.locate(0,0);
    myLCD.set_font((unsigned char*) Arial12x12,32,127,false); //variable width disabled
    
    // initialization
    // GYRO_CONFIG(0x1B)
    //  set Gyroscope resolution to +/- 250deg/sec
    // ACCEL_CONFIG(0x1C)
    //  set Accelerometer resolution to +/- 2G
    // PWR_MGMT_1(0x6B)
    //  set CLKSEL to GyroX reference
    //  exit from sleep mode
    myLCD.printf ("Mounting SD Card.\n");
    int err = fs.mount(bd);
    myLCD.printf ("%s\n",(err ? "Mount Failed. :(" : "OK"));
    // Check if data.txt exists
    FILE *f = fopen ("/fs/data.txt", "r+");
    if (!f) {   // file does not exsts.
        myLCD.printf ("File does not exists. Create..\n");
        f = fopen ("/fs/data.txt", "w+");
        fclose(f);
    }
    
    i2c_cmd[0]=0x75;
    MPU6050.write(MPU6050_addr,i2c_cmd,1);
    MPU6050.read(MPU6050_addr|1,i2c_dat,1);
    //myLCD.printf ("Reg 0x75 = %x\r\n",i2c_dat[0]);

    i2c_cmd[0]=0x1B;
    MPU6050.write(MPU6050_addr,i2c_cmd,1);
    MPU6050.read(MPU6050_addr|1,i2c_dat,1);
    //myLCD.printf ("Reg 0x1B = %x\r\n",i2c_dat[0]);

    i2c_cmd[0]=0x1C;
    MPU6050.write(MPU6050_addr,i2c_cmd,1);
    MPU6050.read(MPU6050_addr|1,i2c_dat,1);
    //myLCD.printf ("Reg 0x1C = %x\r\n",i2c_dat[0]);

    i2c_cmd[0]=0x6B;
    MPU6050.write(MPU6050_addr,i2c_cmd,1);
    MPU6050.read(MPU6050_addr|1,i2c_dat,1);
    //myLCD.printf ("Reg 0x6B = %x\r\n",i2c_dat[0]);
   
    i2c_cmd[1]=i2c_dat[0] & 0xbf;   // clear sleep bit
    MPU6050.write(MPU6050_addr,i2c_cmd,2, true);
    MPU6050.read(MPU6050_addr,i2c_dat,1);
    //myLCD.printf ("Reg 0x6B = %x\r\n",i2c_dat[0]);  // check

    while(1) {                       // clear screen
        myLCD.locate(0,100);
        if (!trig) {
            record = !record;
        }

        //myLCD.printf("Display ID: %.8X\r\n", myLCD.tftID);
        // Read LM75A temp sensor
        //temperature = LM75A.read_T();
        i2c_cmd[0] = 0x0;
        LM75A.write(LM75A_addr,i2c_cmd, 1);
        LM75A.read(LM75A_addr | 1, i2c_dat, 2);
        unsigned short temperature = ((i2c_dat[0] << 8) + i2c_dat[1]) >> 8;

        // Read MPU6050 accelerometer & gyro
        i2c_cmd[0]=0x3B;
        MPU6050.write(MPU6050_addr,i2c_cmd,1);
        MPU6050.read(MPU6050_addr|1,i2c_dat,14);
        ax=(((int16_t)i2c_dat[0]) << 8) | i2c_dat[1];
        ay=(((int16_t)i2c_dat[2]) << 8) | i2c_dat[3];
        az=(((int16_t)i2c_dat[4]) << 8) | i2c_dat[5];
        tp=(((int16_t)i2c_dat[6]) << 8) | i2c_dat[7];
        gx=(((int16_t)i2c_dat[8]) << 8) | i2c_dat[9];
        gy=(((int16_t)i2c_dat[10]) << 8) | i2c_dat[11];
        gz=(((int16_t)i2c_dat[12]) << 8) | i2c_dat[13];
        float temp = (tp / 340) + 36.53;

        myLCD.printf ("Accelerometer:\r\n");
        myLCD.printf ("  AX : %6d\r\n",ax);
        myLCD.printf ("  AY : %6d\r\n",ay);
        myLCD.printf ("  AZ : %6d\r\n",az);
        myLCD.printf ("Temp : %6d\r\n",int (temp));
        myLCD.printf ("Gyroscope\r\n");
        myLCD.printf ("  GX : %6d\r\n",gx);
        myLCD.printf ("  GY : %6d\r\n",gy);
        myLCD.printf ("  GZ : %6d\r\n",gz);
        myLCD.printf ("System Temp=%d\r\n",int(temperature));

        if (record) {
            myLCD.printf ("Recording.....\n");
                FILE *f = fopen("/fs/data.txt","a");    // open with append mode
                err = fprintf (f,"AX: %6d, AY: %6d, AZ: %6d, GX: %6d, GY: %6d, GZ: %6d\r\n",ax,ay,az,gx,gy,gz);
                if (err < 0) {
                    myLCD.printf ("Write to SD failed.\n");
                    fclose (f);   // close each time
                }
                err = fclose (f);
        }
        else {
            myLCD.printf("Waiting........\n");
        }
        wait_us(1000000);
    }
}

mbedは、なんでだかやってみないと判らない、ということの繰り返しで、思ったよりもだいぶ苦労してしまいました。ハードウエア記述がソースから切り出され、IDE側でハンドリングしようとするくだりは、u-boot device treeの頃を思い出すようです。つまり、いかんせんそれまでのmbed2での蓄積が大きくて、先人さま方のblogもなかなか欲しい状況にヒットしない、ということになります。しかしまぁ、Arduinoとmbed、どっちかだけで済ませたいなぁ…。


2021年1月30日土曜日

NanoPi NEOにSPI LCD - OpenCVにpythonのthread

 あるカメラ(モジュール)の動作確認・設定を行うための専用の端末が必要になりました。もちろん、そんなものはWindowsなりLinuxなりのPCでやるよう構築できるものと思いますが、現場作業ものだし、『専用』なので、それだけの投資(あるいは維持)をしないとならなくなってしまいます。
そういうこと?で、なるべくお手軽にしておきたいわけで、先日せっかくTinyDRM使って『小型端末化』できるようになったNanoPi NEOにご登場いただこうか、というところです。

OS

いつものごとくarmbian使うんですが、armbianサイトに置かれているbusterイメージ、ぶっ壊れているみたいで、torrentしても、その時はunxzすることできず。手っ取り早くbionicで進めることにしました。
一時は本家Debianばっかり使ってたんですが、rock64でayufanを使う必要があったところから、だんだんubuntuが増えてきてます。(ちなみに、インストール後数日経過してるところで書いてるんですが、今現在focalにupgrade中です…)

まずはシリアルコンソールで立ち上げ、先日のdtsを持ってきてoverlayを追加し、キーボードとマウス、それにUSBドングルタイプのWiFiを用意してセットアップ開始。何分、NEO2ではなくNEOなので、軽くしておきたくGUIとしてxubuntuを選択。

OpenCV

C++から使う能力は私にはないので、python3で使うべくopencv-pythonをpipでインストール。想定どおり、build過程でout of memoryしてこけるので、1Gのfilesystem swapを追加してしまいます。これ、安定して稼働してくれれば無くしても大丈夫なはず。
しかしout of spaceで再度build中断。おや?filesystem full?そんなはずは…と思ったら、どうも、opencv-pythonをソースからbuildさせるとダウンロードしてくるため、/tmpを食いつぶしてしまいそれで起こっているようです。
このため、TMPDIRを設定してやって仕切り直し。
これで問題ないかなーと言うとさにあらず。 No module named 'skbuild' ?なんじゃこりゃ、と、お助けを乞うたところ、pipが古いのだそうな。セットアップしたばっかりなのに…(ったって、そのインストールイメージ作られたのがいつなのか、と言う問題なんだから…)。その他に、setuptoolsもupgradeしておく必要があるようです。

さすがにopencv-pythonのbuildには数時間必要。計ってはないんですが、12時間くらいかかってますかね…。

動作確認。pythonでopencv使ってjpeg読んで、LCDサイズに合わせて表示させました。


 

『箱』

NanoPi NEOには、上の写真に見て取れる、アクリル板を使ったケースというかガードというかなんというかがオプション品にあるんですが、さすがに、用途的に、LCDと、それをつなぐケーブルをぷらぷらさせておくわけにはいきません。そのため、何らか箱に収めたいんですが、NanoPi NEOは高さが結構ある関係で、LCDの配置次第で、大体40mm以上高さがあるケースが必要になります。これって結構な制約条件で、高さ方向として約40mmとろうとすると、結構大きなケースになってしまいます。NanoPiとLCDを横に並べた場合でも、高さは概ね25mm必要です。

その昔、円筒形のガムの空きボトルを使ったことがあります。あれ、ポリエチレンで、カッターで切れるし、直径が70mm近くあるんですが、コネクタ類(USB、Ether)をちゃんと出したいしLCDが2.8"だと2:1くらいに縦割りし、横倒しして使うイメージになるため、転がらないような対策も必要だし、あまりよろしい結果になりそうにありません。アクリルケースは意外と希望サイズに近いものがなく、かつ加工の際に割れるとな〜、放熱もちょっとな〜と思うと、選定しづらい感じ。アクリルのパネルから6枚切り出して…と言うのも考えましたが、コーナーに角材を入れないと接着強度が気になる。

結局アルミケースか、と探してみると、やはり高さネックでなかなかちょうど良いのがなく…と、これだけで結構な時間悩んで、最終的にたどり着いたのがタカチのTS-1S。傾斜ケースで、上面にLCD用の開口とLCD取り付け用の穴を開けないとならないので、結構な作業です。で、側面にUSBとEthernet、USBは拡張コネクタからも引っ張り出しておくので、上面に5つ、側面に3つの都合8個穴を開けることになります。上面はLCDパネルとキャリアボードのサイズの関係で左右が均等にならないし、NEOのRJ-45とUSBはあんまり間隔が開いてないので、そもそもそんな穴あけあんまり精度良くやる自信ないわぁ、と思いつつ、電ドリとハンドニブラー、ヤスリで何とか開けました。案の定若干ずれてますが…。ケースの保護フィルムは張ったままです、多分永遠に(笑)。


これはほぼ最終状態のスナップショットなので、アプリショートカットやら何やら設定した後になります。

 

おまけ -- focalへのupgrade

 これを書いている間に、focalがリリースされたけどインストールする?と、ダイアログがでました。もちろん進めるんですが、do-release-upgradeで何かエラーが起こってる。なになに?KeyError: 'suite'?
これは(も?)初体験に付き調べ回っていると、事象はこちらのサイトで話されていることと同じようです。GzipIndexが使われているのは02-armbian-compress-indexesだけなので、こいつを変更してやると、あとはupgradeが進みました。


2021年1月8日金曜日

NanoPi NEOにSPI LCD - TinyDRM

  ここのところ、GPSや多軸加速度センサを使った工作をarduinoでやったりしていたのですが、あるデバイスをコントロールするための、CUIあるいはGUI付きの箱を一つ作る必要がありそうで、以前このblogにも書いた、NanoPi NEO + LCDで構成することを考えました。

以前の記事、もう2年以上前になっているわけですが、当時はLinux4系列で、fbtftを使っていました。その時でもfbtftはstagingで、すでにtinydrmの紹介はされていましたが、多くの情報があるfbtftをそのまま使い、そのメモ書きを残していたわけです。

いま、armbianはLinux5.9、TinyDRMを使わなければなりません。

fbtftでは、fbtft_deviceを呼ぶ際に、使用するgpio端子を記述でき、initコードもquick hack的に試すことができました。TinyDRMではdevice treeを作成する必要がありますし、使用できるコントローラも限られそうです。initコードを変更する必要があった場合には、ドライバを変更しないとならないようです。手元には、いずれもaitendoで入手したものですが、SPIを使用するQVGA LCD、コントローラがili9328のもの、ili9341のものがあり、ドライバの観点では、ili9341なら容易に動かせるかもしれません。(スタンドアロンでili9328を動かすコードを公開されているかたが居られるので、それを参考に、notroさんが公開しているili9325用のドライバを修正できるかもしれませんが、将来…。)

 ということで始めてはみたものの。

Device treeのドキュメントは存在してはいるようですが、汎用的なため、体系的理解が進めば良いのかもしれませんが、LCDを動かすためにはちょっとだいぶ重荷だったので、世間の皆様に頼ることにしました。

試行錯誤の結果、次のようなdevice treeを作成: 

/dts-v1/;
/plugin/;

/ {
        compatible = "allwinner,sun8i-h3";

        fragment@0 {
                target = <&spi0>;
                __overlay__ {
                        status = "okay";
                        spidev@0{
                                status = "disabled";
                        };
                };
        };

        fragment@1 {
                target = <&pio>;
                __overlay__ {
                        M028C9341SD_pins: M028C9341SD_pins {
                                pins = "PG9", "PA1";
                                function = "gpio_out";
                        };
                };
        };

        fragment@2 {
                target = <&spi0>;
                __overlay__ {
                        /* needed to avoid dtc warning */
                        #address-cells = <1>;
                        #size-cells = <0>;

                        M028C9341SD: M028C9341@0{
                                compatible = "mi,mi0283qt";
                                /* compatible = "ilitek,ili9341"; */
                                reg = <0>;

                                spi-max-frequency = <32000000>;
                                spi-cpol;
                                spi-cpha;
                                buswidth = <8>;
                                rotation = <0>;
                                reset-gpios = <&pio 0 201 0>; /* PG9 */
                                dc-gpios = <&pio 0 1 0>;    /* PA1 */
                        };
                };
        };
};

相手はNanoPi NEOなのでSPI0、それにD/C(RS)と、resetで使用するピン、それぞれPA1とPG9を使っていますが、それを宣言してやります。

これを適当な名前でsaveして、armbian-add-overlayし、rebootすると、TinyDRM関連ドライバおよびmi0283qtがロードされ、コンソールとしてブートメッセージが流れてくれるのを見たときには、ちょっと感動しました(笑)。

SPIを使用するLCDなら、それに合ったドライバを指定し直すことで、上記の構造はそのまま使えるはずです。このLCDモジュールにはSDカードコネクタもあるので、SPIモードであれば動かすことができると思いますし、もし、タッチパネル付きのものの場合は、それに応じた追加指定が必要です。NanoPiNEOの場合SPIは1ポートしかなく、armbian-configを使ってSPI1用のCSを追加してやれば、SPIの仕様的にはそれら更なるデバイスの追加は可能なように思えますが、LCDのパフォーマンス的にどうなのかな?とは思うところです。

 

さて、これを元に、 「箱」に仕立てる算段をすることにしましょう。キー入力とかどうしようかな…。