AndroidStudioでTensorflowデモを動かす。

はじめに

TensorflowのAndroidのデモを動かそうとしたところ、 いろいろハマった上にまとまった情報がなかったのでメモっておく。

ソースの取得

まず--recurse-submodules忘れた。これは完全に自分のミス。

$ git clone --recurse-submodules https://github.com/tensorflow/tensorflow.git

修正点

手で修正したファイルは次のとおり。

  • tensorflow/examples/android/AndroidManifest.xml
  • tensorflow/examples/android/build.gradle

AndroidManifest.xml

基本的にはAndroidStudioが色々やってくれるのでそれに従う。

diff --git a/tensorflow/examples/android/AndroidManifest.xml b/tensorflow/examples/android/AndroidManifest.xml
index 5c47ce6b67..bea3683102 100644
--- a/tensorflow/examples/android/AndroidManifest.xml
+++ b/tensorflow/examples/android/AndroidManifest.xml
@@ -16,23 +16,29 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     package="org.tensorflow.demo">
-
     <uses-permission android:name="android.permission.CAMERA" />
-    <uses-feature android:name="android.hardware.camera" />
-    <uses-feature android:name="android.hardware.camera.autofocus" />
+    <uses-feature android:name="android.hardware.camera"/>
+    <uses-feature android:name="android.hardware.camera.autofocus"/>
+    <uses-feature android:name="android.software.leanback"/>
+    <uses-feature android:name="android.hardware.touchscreen"/>
+
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
     <uses-permission android:name="android.permission.RECORD_AUDIO" />
 
     <uses-sdk
         android:minSdkVersion="21"
-        android:targetSdkVersion="23" />
+        android:targetSdkVersion="27" />
 
-    <application android:allowBackup="true"
+    <application
+        android:hardwareAccelerated="true"
+        android:allowBackup="true"
         android:debuggable="true"
         android:label="@string/app_name"
         android:icon="@drawable/ic_launcher"
-        android:theme="@style/MaterialTheme">
+        android:theme="@style/MaterialTheme"
+        tools:ignore="GoogleAppIndexingWarning,HardcodedDebugMode,MissingTvBanner">
 
         <activity android:name="org.tensorflow.demo.ClassifierActivity"
             android:screenOrientation="portrait"

一番ハマったのはandroid:hardwareAccelerated="true"

手持ちのスマートフォンのカメラがしょぼく、CameraAPI2が使用できなかったためレガシーにフォールバックするのだが、 これがハードウェアアクセラレーションを有効化していないと、画面が真っ黒になってしまうという問題がある。

TextureViewでonSurfaceTextureAvailableが呼ばれないことに気づいたのでわかった。

build.gradle

diff --git a/tensorflow/examples/android/build.gradle b/tensorflow/examples/android/build.gradle
index 0767726aa9..20c4e4df9b 100644
--- a/tensorflow/examples/android/build.gradle
+++ b/tensorflow/examples/android/build.gradle
@@ -24,23 +24,25 @@ getProject().setBuildDir('gradleBuild')
 
 buildscript {
     repositories {
+        google()
         jcenter()
     }
 
     dependencies {
-        classpath 'com.android.tools.build:gradle:3.0.1'
+        classpath 'com.android.tools.build:gradle:3.3.1'
         classpath 'org.apache.httpcomponents:httpclient:4.5.4'
     }
 }
 
 allprojects {
     repositories {
+        google()
         jcenter()
     }
 }
 
 // set to 'bazel', 'cmake', 'makefile', 'none'
-def nativeBuildSystem = 'bazel'
+def nativeBuildSystem = 'none'
 
 // Controls output directory in APK and CPU type for Bazel builds.
 // NOTE: Does not affect the Makefile build target API (yet), which currently
@@ -76,14 +78,14 @@ project.ext.TMP_DIR   = project.buildDir.toString() + '/downloads'
 apply plugin: 'com.android.application'
 
 android {
-    compileSdkVersion 23
-    buildToolsVersion '26.0.2'
+    compileSdkVersion 27
+
 
     if (nativeBuildSystem == 'cmake') {
         defaultConfig {
             applicationId = 'org.tensorflow.demo'
             minSdkVersion 21
-            targetSdkVersion 23
+            targetSdkVersion 27
             ndk {
                 abiFilters "${cpuType}"
             }
@@ -189,6 +191,6 @@ apply from: "download-models.gradle"
 
 dependencies {
     if (nativeBuildSystem == 'cmake' || nativeBuildSystem == 'none') {
-        compile 'org.tensorflow:tensorflow-android:+'
+        implementation 'org.tensorflow:tensorflow-android:+'
     }
 }

ポイントとなる修正点は次のとおり。

  1. リポジトリへのgoogleの追加(2ヶ所)
  2. def nativeBuildSystem = 'none'
  3. implementation 'org.tensorflow:tensorflow-android:+'

これらの修正を行わないとビルドが通らなかった。

APKのインストールに失敗してアプリが開始できない

アプリやデバッグを開始しようとすると次のメッセージがでてアプリが開始できない。 APKのインストールで失敗するようだった。

java.io.IOException: 接続が相手からリセットされました
Error while Installing APK

たぶん、ここDisable Instant Runが効いたと思う。

Disable Instant Run (Android Document)

To disable Instant Run:

1. Open the Settings or Preferences dialog. (For Mac, Android Studio -> Preferences)
2. Navigate to Build, Execution, Deployment > Instant Run.
3. Uncheck the box next to Enable Instant Run.

ただ、この操作をしてもしばらくうまく行かなくて、.gradle.ideaを削除して、AndroidStudioを再起動したらうまくいった記憶がある。(既に曖昧)

Yoctoターゲットのセルフコンパイル環境でカーネルモジュールをビルド

はじめに

Yoctoのセルフコンパイル環境でout-of-treeのカーネルモジュールをビルドする。

普通にビルドしようとエラーが発生するのでその回避方法をメモしておく。

hello-mod

今回はrecipe-skeleton/recipes-kernelにあるhello-modラズベリーパイ3上でビルドする。

Yoctoのイメージ

今回使用するセルフコンパイル環境を作成するには、local.confに次の内容を追加する。

# for raspberrypi3
MACHINE = "raspberrypi3"
DL_DIR ?= "${TOPDIR}/../downloads"

# enable uart
ENABLE_UART = "1"

# enable self-compile
EXTRA_IMAGE_FEATURES_append = " dev-pkgs tools-debug tools-sdk"

# add kernel soruce
IMAGE_INSTALL_append = "kernel-devsrc"

カーネルソースを含むパッケージkernel-devsrcを追加する。

作成するモジュール

~/helloにソースとMakefileを作成する。

$ mkdir ~/hello
$ cd hello

~/hello/hello.cを次の内容で作成する。

#include <linux/module.h>

int init_module(void)
{
    printk("Hello World!\n");
    return 0;
}

void cleanup_module(void)
{
    printk("Goodbye Cruel World!\n");
}

MODULE_LICENSE("GPL");

~/hello/Makefileを次の内容で作成する。

obj-m := hello.o

SRC := $(shell pwd)

all:
    $(MAKE) -C $(KERNEL_SRC) M=$(SRC)

modules_install:
    $(MAKE) -C $(KERNEL_SRC) M=$(SRC) modules_install

clean:
    rm -f *.o *~ core .depend .*.cmd *.ko *.mod.c
    rm -f Module.markers Module.symvers modules.order
    rm -rf .tmp_versions Modules.symvers

このMakefileではKERNEL_SRCを変数にしているので、make実行に次のようにするか

# cd ~/hello
# make KERNEL_SRC=/lib/modules/`uname -r`/build

予め環境変数に登録しておく。

# export KERNEL_SRC=/lib/modules/`uname -r`/build
# make

発生するエラーの内容

初期状態でモジュールをビルドすると次のようなエラーが発生する。

# make KERNEL_SRC=/lib/modules/`uname -r`/build
make -C /lib/modules/4.14.79/build M=/home/root/hello
make[1]: Entering directory '/lib/modules/4.14.79/build'
make[1]: Warning: File 'scripts/Makefile.ubsan' has modification time 601 s in the future
make[2]: Warning: File 'scripts/Makefile.lib' has modification time 601 s in the future
  AR      /home/root/hello/built-in.o
  CC [M]  /home/root/hello/hello.o
In file included from ./include/asm-generic/int-ll64.h:11,
                 from ./arch/arm/include/uapi/asm/types.h:5,
                 from ./include/uapi/linux/types.h:5,
                 from ./include/linux/types.h:6,
                 from ./include/linux/list.h:5,
                 from ./include/linux/module.h:9,
                 from /home/root/hello/hello.c:1:
./include/uapi/asm-generic/int-ll64.h:12:10: fatal error: asm/bitsperlong.h: No such file or directory
 #include <asm/bitsperlong.h>
          ^~~~~~~~~~~~~~~~~~~
compilation terminated.
make[2]: *** [scripts/Makefile.build:335: /home/root/hello/hello.o] Error 1
make[1]: *** [Makefile:1527: _module_/home/root/hello] Error 2
make[1]: Leaving directory '/lib/modules/4.14.79/build'
make: *** [Makefile:6: all] Error 2

fatal error: asm/bitsperlong.h: No such file or directorybitsperlong.hが見つからないというエラーが出る。

エラー回避の方法

まずは、ラズベリーパイの時刻が狂っているので現在時刻に設定する。

$ date -s "MM/DD hh:mm YYYY"

日付を合わせておかないとwarning: Clock skew detected. Your build may be incomplete.という警告が出る。 実害はなさそうだが気持ち悪いので対策しておく。

次にカーネルソースのscriptsをビルドする。

$ cd /lib/modules/`uname -r`/build
$ make scripts

bitsperlong.hのエラーはこちらで対策できる。

再びビルド

再度モジュールをビルドしてみる。

# cd ~/hello
# make KERNEL_SRC=/lib/modules/`uname -r`/build
make -C /lib/modules/4.14.79/build M=/home/root/hello
make[1]: Entering directory '/lib/modules/4.14.79/build'
  AR      /home/root/hello/built-in.o
  CC [M]  /home/root/hello/hello.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /home/root/hello/hello.mod.o
  LD [M]  /home/root/hello/hello.ko
make[1]: Leaving directory '/lib/modules/4.14.79/build'

hello.koが生成される。

ほかのヘッダファイルでエラー

たとえば、hello.cの先頭の方にに次の行を追加する。

#include <linux/module.h>
#include <linux/unistd.h>

...(snip)...

すると、makeで次のエラーが発生する。

# make KERNEL_SRC=/lib/modules/`uname -r`/build
make -C /lib/modules/4.14.79/build M=/home/root/hello
make[1]: Entering directory '/lib/modules/4.14.79/build'
  CC [M]  /home/root/hello/hello.o
In file included from ./arch/arm/include/asm/unistd.h:16,
                 from ./include/uapi/linux/unistd.h:8,
                 from /home/root/hello/hello.c:2:
./arch/arm/include/uapi/asm/unistd.h:21:10: fatal error: asm/unistd-eabi.h: No such file or directory
 #include <asm/unistd-eabi.h>
          ^~~~~~~~~~~~~~~~~~~
compilation terminated.
make[2]: *** [scripts/Makefile.build:335: /home/root/hello/hello.o] Error 1
make[1]: *** [Makefile:1527: _module_/home/root/hello] Error 2
make[1]: Leaving directory '/lib/modules/4.14.79/build'
make: *** [Makefile:6: all] Error 2

これは次のコマンドで回避できる。

# cd /lib/modules/`uname -r`/build
# make prepare

最終的な回避策

Yoctoで作成したセルフコンパイル環境でout-of-treeのカーネルモジュールをビルドするためには、 カーネルソースで予めscriptsprepareをmakeしておく必要がある。

# cd /lib/modules/`uname -r`/build
# make scripts
# make prepare

hello.cに次のようなincludeを追加したが、とりあえずのところエラーは発生していない。

#include <linux/module.h>
#include <linux/unistd.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/sched.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <asm/current.h>
#include <asm/uaccess.h>
#include <asm/io.h>

...(snip)...

PlatformIOでSTM32F4Discoveryのプログラムを書く

はじめに

組み込み向けの開発環境を構築する手段としてPlatformIOというものがある。

PlatformIOはPythonスクリプトで、必要に応じてクロスコンパイル用のツールチェインなどダウンロードしてくれたり、 それぞれのボード向けのスタートアップコードなんかも提供してくれる。

またVSCodeAtomなどのIDEとも連携でき、IDE上でデバッグをすることもできる。

CLionとの連携もできるがこちらはIDE上でのデバッグには対応していない様子だった。

インストール

PlatformIOはpipでインストールできる。

$ sudo pip install platformio

STM32F4Discovery向けの環境を構築

まず、次のコマンドでPlatformIOがSTM32F4Discoveryに対応しているかを確認する。

$ platformio boards | grep -i 'stm32f4disco'
disco_f407vg          STM32F407VGT6  168MHz    1MB     192KB  ST STM32F4DISCOVERY

disco_f407vgという名前で使えるらしいので、環境をつくってみる。

$ mkdir stm32f4disco_pio
$ cd stm32f4disco_pio
$ platformio init --board disco_f407vg

作成されたもの

プロジェクトを作成すると次のものが作成されます。

生成物 概要
platformio.ini プロジェクト設定ファイル
src ソースを格納するディレクト
lib プロジェクトして使用する(プライベートな)ライブラリを格納するディレクト

とりあえず何か動かしてみる

とりあえずsrc/hello.cppを次の内容で作成する。

#include <Arduino.h>

void setup() {
    Serial.begin(115200);
}

void loop() {
    Serial.print("Hello world\r\n");
    delay(1000);
}

ST-LINK経由で書き込むので、platformio.iniを次のようにする。

[env:disco_f407vg]
platform = ststm32
board = disco_f407vg
framework = arduino

upload_protocol = stlink

次のコマンドでプログラムをボードに書き込む。

$ platformio run --target upload

UARTの出力を確認

STM32F4DiscoveryとPCをUSB-TTL変換ケーブルで接続する。

ケーブル側 STM32F4Discovery
5V(赤) 5V
GND(黒) GND
TXD(緑) PA10(RX1)
RXD(白) PA9(TX1)

Ubuntuでは/dev/ttyUSB0として見えるのでminicomなどで開く。

ボードのリセットボタンを押した後、こんな感じで1秒ごとに表示されていればOK。

Hello world
Hello world
Hello world
Hello world
Hello world
Hello world
Hello world
Hello world

raspberypi3 B+ EthernetのLEDについて

はじめに

ラズベリーパイ3 Model B+ではLANケーブルを接続した時に点滅するLEDのうち、 なぜか1つしか光らないという話題になったので、これについて調べた。

EthernetのLED

LANケーブルのコネクタには緑と黄色のLEDが1つずつ付いている。 ケーブルを接続すると動作状況に応じて点滅するが、なぜかどちらか1つしか光らない。

ここによると次のようなことらしい。

AFAIK yellow comes on for GigE connection. Since I only have 100M hub I only have green LED on :-)

ギガビットの場合は黄色が点滅し、100Mの場合は緑が点滅するらしい。

f:id:mickey_happygolucky:20190212133100j:plain
ギガビット(黄)

f:id:mickey_happygolucky:20190212133113j:plain
100M(緑)

なるほどぉ。

NuttX STM32F4Discovery+RNDISでEthernet(ついでにMQTT)

はじめに

NuttXはRNDISドライバを持っている。

STM32F4DiscoveryはRNDISドライバを使用すると、USBでEthernetバイスとして通信できるようになる。

意外なほど簡単に使用することができる。

コンフィグレーション

$ tools/configure.sh -l configs/stm32f4discovery/rndis

動作確認

STM32F4Discovery側

初期状態では、デバイスは認識しているがIPアドレスは振られていない。

nsh> ifconfig
eth0    Link encap:Ethernet HWaddr 00:e0:de:ad:ca:fe at UP
        inet addr:0.0.0.0 DRaddr:0.0.0.0 Mask:0.0.0.0

lo      Link encap:Local Loopback at UP
        inet addr:127.0.0.1 DRaddr:127.0.0.1 Mask:255.0.0.0

             IPv4   TCP   UDP  ICMP
Received     0008  0000  0008  0000
Dropped      0000  0000  0000  0000
  IPv4        VHL: 0000   Frg: 0000
  Checksum   0000  0000  0000  ----
  TCP         ACK: 0000   SYN: 0000
              RST: 0000  0000
  Type       0000  ----  ----  0000
Sent         0005  0000  0005  0000
  Rexmit     ----  0000  ----  ----

次のコマンドでIPアドレスを割り当てる。

nsh> ifconfig eth0 192.168.20.100

IPアドレスを指定すると、ネットマスクとゲートウェイは自動的に設定される。

nsh> ifconfig
eth0    Link encap:Ethernet HWaddr 00:e0:de:ad:ca:fe at UP
        inet addr:192.168.20.100 DRaddr:192.168.20.1 Mask:255.255.255.0

lo      Link encap:Local Loopback at UP
        inet addr:127.0.0.1 DRaddr:127.0.0.1 Mask:255.0.0.0

             IPv4   TCP   UDP  ICMP
Received     0016  0000  0016  0000
Dropped      0000  0000  0000  0000
  IPv4        VHL: 0000   Frg: 0000
  Checksum   0000  0000  0000  ----
  TCP         ACK: 0000   SYN: 0000
              RST: 0000  0000
  Type       0000  ----  ----  0000
Sent         0011  0000  0011  0000
  Rexmit     ----  0000  ----  ----

PC側

RNDISを有効化してSTM32F4DiscoveryのマイクロUSBとPCのUSBポートを接続すると、 次のように認識される。

$ dmesg
...(snip)...
[187044.300595] usb 1-7.4: new full-speed USB device number 9 using xhci_hcd
[187044.646579] usb 1-7.4: New USB device found, idVendor=584e, idProduct=5342
[187044.646584] usb 1-7.4: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[187044.646586] usb 1-7.4: Product: RNDIS gadget
[187044.646588] usb 1-7.4: Manufacturer: NuttX
[187044.646590] usb 1-7.4: SerialNumber: 1234
[187044.672267] usbcore: registered new interface driver cdc_ether
[187044.674191] rndis_host 1-7.4:1.0: skipping garbage
[187044.689562] rndis_host 1-7.4:1.0: dev can't take 1558 byte packets (max 660), adjusting MTU to 602
[187044.739995] rndis_host 1-7.4:1.0 eth0: register 'rndis_host' at usb-0000:15:00.0-7.4, RNDIS device, a0:e0:de:ad:ca:fe
[187044.740051] usbcore: registered new interface driver rndis_host
[187044.744102] usbcore: registered new interface driver rndis_wlan
[187044.752038] rndis_host 1-7.4:1.0 enxa0e0deadcafe: renamed from eth0

enxa0e0deadcafeというネットワークデバイスとして見えている。

これもIPアドレスは振られていないので、ネットワーク設定画面で192.168.20.1に設定する。

pingが通ればOK。

$ ping 192.168.20.100
PING 192.168.20.100 (192.168.20.100) 56(84) bytes of data.
64 bytes from 192.168.20.100: icmp_seq=2 ttl=64 time=0.472 ms
64 bytes from 192.168.20.100: icmp_seq=3 ttl=64 time=0.460 ms
64 bytes from 192.168.20.100: icmp_seq=4 ttl=64 time=0.446 ms
64 bytes from 192.168.20.100: icmp_seq=5 ttl=64 time=0.438 ms

ついでにSTM32F4Discovery側からpingも確認しておく。

nsh> ping 192.168.20.1
PING 192.168.20.1 56 bytes of data
56 bytes from 192.168.20.1: icmp_seq=0 time=0 ms
56 bytes from 192.168.20.1: icmp_seq=1 time=0 ms
56 bytes from 192.168.20.1: icmp_seq=2 time=0 ms
56 bytes from 192.168.20.1: icmp_seq=3 time=0 ms
56 bytes from 192.168.20.1: icmp_seq=4 time=0 ms
56 bytes from 192.168.20.1: icmp_seq=5 time=0 ms
56 bytes from 192.168.20.1: icmp_seq=6 time=0 ms
56 bytes from 192.168.20.1: icmp_seq=7 time=0 ms
56 bytes from 192.168.20.1: icmp_seq=8 time=0 ms
56 bytes from 192.168.20.1: icmp_seq=9 time=0 ms
10 packets transmitted, 10 received, 0% packet loss, time 10100 ms

MQTT

Ethernetが使えるので当然MQTTも使えるはず。

前回の要領でMQTTを有効化する。

nsh> ifconfig eth0 192.168.20.100
nsh> ifconfig
eth0    Link encap:Ethernet HWaddr 00:e0:de:ad:ca:fe at UP
        inet addr:192.168.20.100 DRaddr:192.168.20.1 Mask:255.255.255.0

lo      Link encap:Local Loopback at UP
        inet addr:127.0.0.1 DRaddr:127.0.0.1 Mask:255.0.0.0

             IPv4   TCP   UDP  ICMP
Received     0039  0000  0001  0000
Dropped      0002  0000  0000  0000
  IPv4        VHL: 0002   Frg: 0000
  Checksum   0000  0000  0000  ----
  TCP         ACK: 0000   SYN: 0000
              RST: 0000  0000
  Type       0000  ----  ----  0000
Sent         0002  0000  0002  0000
  Rexmit     ----  0000  ----  ----
nsh> ping 192.168.20.1
PING 192.168.20.1 56 bytes of data
56 bytes from 192.168.20.1: icmp_seq=0 time=0 ms
56 bytes from 192.168.20.1: icmp_seq=1 time=0 ms
56 bytes from 192.168.20.1: icmp_seq=2 time=0 ms
56 bytes from 192.168.20.1: icmp_seq=3 time=0 ms
56 bytes from 192.168.20.1: icmp_seq=4 time=0 ms
56 bytes from 192.168.20.1: icmp_seq=5 time=0 ms
56 bytes from 192.168.20.1: icmp_seq=6 time=0 ms
56 bytes from 192.168.20.1: icmp_seq=7 time=0 ms
56 bytes from 192.168.20.1: icmp_seq=8 time=0 ms
56 bytes from 192.168.20.1: icmp_seq=9 time=0 ms
10 packets transmitted, 10 received, 0% packet loss, time 10100 ms
nsh> mqttc 192.168.20.1
mqttc is ready to begin publishing the time.
Press ENTER to publish the current time.
Press CTRL-D (or any other key) to exit.

mqttc published : "The time is 1970-01-01 00:00:57"
packet_id = 0x0000C96A,536916472
mqttc published : "The time is 1970-01-01 00:00:58"
packet_id = 0x000064B5,536916452
mqttc published : "The time is 1970-01-01 00:00:59"
packet_id = 0x0000865A,536916432
mqttc published : "The time is 1970-01-01 00:00:59"
packet_id = 0x0000432D,536916412
mqttc published : "The time is 1970-01-01 00:01:00"
packet_id = 0x00009596,536916392
mqttc published : "The time is 1970-01-01 00:01:00"
packet_id = 0x00004ACB,536916372
mqttc published : "The time is 1970-01-01 00:01:00"
packet_id = 0x00009165,536916352
mqttc published : "The time is 1970-01-01 00:01:00"
packet_id = 0x0000FCB2,536916332
mqttc published : "The time is 1970-01-01 00:01:00"
packet_id = 0x00007E59,536916312

mqttc disconnecting from 192.168.20.1

PC側の様子。

$ mosquitto_sub -t datetime
The time is 1970-01-01 00:00:57
The time is 1970-01-01 00:00:58
The time is 1970-01-01 00:00:59
The time is 1970-01-01 00:00:59
The time is 1970-01-01 00:01:00
The time is 1970-01-01 00:01:00
The time is 1970-01-01 00:01:00
The time is 1970-01-01 00:01:00
The time is 1970-01-01 00:01:00

おまけ

簡単に試せるようにGithubリポジトリを用意した。

$ git clone https://github.com/mickey-happygolucky/nuttx.git -b mqtt
$ git clone https://github.com/mickey-happygolucky/apps.git -b mqtt
$ cd nuttx
$ tools/configure.sh -l configs/stm32f4discovery/rndis_mqtt
$ make -j4
$ st-flash write nuttx.bin 0x8000000

NuttX STM32F4Discovery+ENC28J60でMQTT

はじめに

STM32F4DiscoveryでEthernetが使えるようになったのでMQTTを動かしてみる。

既にNuttXでMQTTを動作させた例がないかWebで調べたところ、こんなものを見つけた。

リンク先では、MQTT-Cを動かそうとしていて問題が発生したというissueで、最終的には動作したということだった。

その時にdarcygさんが作成したパッチが参照できるので、これを参考に作業することにした。

パッチの修正

MQTT-Cはアプリケーションになるため、appsのリポジトリに対しての修正となる。

このパッチはそのままではうまく当たってくれないので、修正する必要がある。

パッチをそのまま載せると長いので、Gistに貼る。

コンフィグレーション

MQTT-Cを組み込むために、次のコンフィグレーションを有効化する.

CONFIG_NETUTILS_MQTTC

Application Configuration
  -> Network Utilities
    -> MQTTC client library

CONFIG_SYSTEM_MQTTC

Application Configuration
  -> System Libraries and NSH Add-Ons
    -> 'mqttc' command

動作確認

UbuntuにMosquittoをインストールしSubscriberを実行し、NuttX側でPublishする。

IPアドレス
PC 192.168.10.1
NuttX 192.168.10.100

Mosquittoのインストール

次のコマンドでインストールする。

$ sudo apt install mosquitto mosquitto-clients

Subscriberの実行

MQTT-CのPublisherのサンプルがdatetimeというトピックをPublishするので、それをSubscribeする。

$ mosquitto_sub -t datetime

Publisherの実行

NuttX側で次のコマンドを実行する。パラメータにPC側のIPアドレスを指定する。

$ mqttc 192.168.10.1

実行して少しすると次のように表示される。

mqttc is ready to begin publishing the time.
Press ENTER to publish the current time.
Press CTRL-D (or any other key) to exit.

これ以降、Enterキーを押すごとにデータがPublishされる。

mqttc published : "The time is 1970-01-01 00:00:34"
packet_id = 0x0000B405,268456040
mqttc published : "The time is 1970-01-01 00:00:35"
packet_id = 0x0000EE02,268456020
mqttc published : "The time is 1970-01-01 00:00:36"
packet_id = 0x00007701,268456000
mqttc published : "The time is 1970-01-01 00:00:37"
packet_id = 0x00008F80,268455980
mqttc published : "The time is 1970-01-01 00:00:38"
packet_id = 0x000047C0,268455960
mqttc published : "The time is 1970-01-01 00:00:39"
packet_id = 0x000023E0,268455940
mqttc published : "The time is 1970-01-01 00:00:39"
packet_id = 0x000011F0,268455920
mqttc published : "The time is 1970-01-01 00:00:40"
packet_id = 0x000008F8,268455900

現在時刻をPublishしているが、RTCなどがないためでたらめな時刻となっている。

PC側でNuttXからのデータがきちんとSubscribeできていればOK。

$ mosquitto_sub -t datetime
The time is 1970-01-01 00:00:34
The time is 1970-01-01 00:00:35
The time is 1970-01-01 00:00:36
The time is 1970-01-01 00:00:37
The time is 1970-01-01 00:00:38
The time is 1970-01-01 00:00:39
The time is 1970-01-01 00:00:39
The time is 1970-01-01 00:00:40

リポジトリのクローン

この環境を組み込んだリポジトリのクローンをGithubに作成した。

使用する場合はnuttxのリポジトリとして次のものを使用する。

$ git clone https://github.com/mickey-happygolucky/nuttx.git -b mqtt
$ git clone https://github.com/mickey-happygolucky/apps.git -b mqtt
$ cd nuttx
$ tools/configure.sh -l configs/stm32f4discovery/nsh_enc28j60_mqtt
$ make -j4
$ st-flash write nuttx.bin 0x8000000

NuttX STM32F4Discovery+ENC28J60でEthernet

はじめに

Amazonで購入したEthernetコントローラ基板をSTM32F4Discovery + NuttXで使用できるようにする。

このコントローラ基板はENC28J60が載っている。ENC28J60はMACとPHYの機能を持っているため、ホストとなるボードはSPIで通信できればEthernetが使えるようになる。

ちなみにSTM32F4Discoveryに載っているSTM32F407VGT6MACの機能を持っているのでPHYだけのコントローラでも使用できる。

ENC28J60のドライバ

NuttXにはENC28J60のドライバは存在するが、STM32F4Discoveryの初期化処理でこのドライバを呼び出すようになっていないため、 初期化処理を修正する。

初期化処理の修正

次のファイルを修正している。

  • configs/stm32f4discovery/src/stm32_netinit.c
  • configs/stm32f4discovery/src/stm32_spi.c
  • configs/stm32f4discovery/src/stm32f4discovery.h
diff --git a/configs/stm32f4discovery/src/stm32_netinit.c b/configs/stm32f4discovery/src/stm32_netinit.c
index 36ee755f93..47a450ad5c 100644
--- a/configs/stm32f4discovery/src/stm32_netinit.c
+++ b/configs/stm32f4discovery/src/stm32_netinit.c
@@ -39,6 +39,134 @@
 
 #include <nuttx/config.h>
 
+#ifdef CONFIG_ENC28J60
+#include <stdint.h>
+#include <stdio.h>
+#include <debug.h>
+
+#include <nuttx/spi/spi.h>
+#include <nuttx/net/enc28j60.h>
+
+#include <arch/board/board.h>
+
+#include "chip.h"
+#include "up_arch.h"
+#include "up_internal.h"
+#include "stm32_spi.h"
+
+#include "stm32f4discovery.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+/* Configuration ************************************************************/
+/* ENC28J60
+ *
+ * --- ------ -------------- -----------------------------------------------------
+ * PIN NAME   SIGNAL         NOTES
+ * --- ------ -------------- -----------------------------------------------------
+ *
+ * 29  PA4    PA4-SPI1-NSS   10Mbit ENC28J60
+ * 30  PA5    PA5-SPI1-SCK   10Mbit ENC28J60
+ * 31  PA6    PA6-SPI1-MISO  10Mbit ENC28J60
+ * 32  PA7    PA7-SPI1-MOSI  10Mbit ENC28J60
+ * 34  PE4    (no name)      10Mbps ENC28J60 Interrupt
+ */
+
+/* ENC28J60 is on SPI1 */
+
+#ifndef CONFIG_STM32_SPI1
+# error "Need CONFIG_STM32_SPI1 in the configuration"
+#endif
+
+/* SPI Assumptions **********************************************************/
+
+#define ENC28J60_SPI_PORTNO 1   /* On SPI1 */
+#define ENC28J60_DEVNO      0   /* Only one ENC28J60 */
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+struct stm32_lower_s
+{
+  const struct enc_lower_s lower;    /* Low-level MCU interface */
+  xcpt_t                   handler;  /* ENC28J60 interrupt handler */
+  FAR void                *arg;      /* Argument that accompanies the interrupt */
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+static int  up_attach(FAR const struct enc_lower_s *lower, xcpt_t handler,
+                      FAR void *arg);
+static void up_enable(FAR const struct enc_lower_s *lower);
+static void up_disable(FAR const struct enc_lower_s *lower);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/* The ENC28J60 normal provides interrupts to the MCU via a GPIO pin.  The
+ * following structure provides an MCU-independent mechanixm for controlling
+ * the ENC28J60 GPIO interrupt.
+ */
+
+static struct stm32_lower_s g_enclower =
+{
+  .lower =
+  {
+    .attach  = up_attach,
+    .enable  = up_enable,
+    .disable = up_disable
+  },
+  .handler = NULL,
+  .arg     = NULL
+};
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: struct enc_lower_s methods
+ ****************************************************************************/
+
+static int up_attach(FAR const struct enc_lower_s *lower, xcpt_t handler,
+                     FAR void *arg)
+{
+  FAR struct stm32_lower_s *priv = (FAR struct stm32_lower_s *)lower;
+
+  /* Just save the handler for use when the interrupt is enabled */
+
+  priv->handler = handler;
+  priv->arg     = arg;
+  return OK;
+}
+
+static void up_enable(FAR const struct enc_lower_s *lower)
+{
+  FAR struct stm32_lower_s *priv = (FAR struct stm32_lower_s *)lower;
+
+  DEBUGASSERT(priv->handler);
+  (void)stm32_gpiosetevent(GPIO_ENC28J60_INTR, false, true, true,
+                           priv->handler, priv->arg);
+}
+
+/* REVISIT:  Since the interrupt is completely torn down, not just disabled,
+ * in interrupt requests that occurs while the interrupt is disabled will be
+ * lost.
+ */
+
+static void up_disable(FAR const struct enc_lower_s *lower)
+{
+  (void)stm32_gpiosetevent(GPIO_ENC28J60_INTR, false, true, true,
+                           NULL, NULL);
+}
+#endif /* CONFIG_ENC28J60 */
+
+
 /************************************************************************************
  * Public Functions
  ************************************************************************************/
@@ -50,6 +178,35 @@
 #if defined(CONFIG_NET) && !defined(CONFIG_NETDEV_LATEINIT)
 void up_netinitialize(void)
 {
+#ifdef CONFIG_ENC28J60
+  FAR struct spi_dev_s *spi;
+  int ret;
+
+  /* Assumptions:
+   * 1) ENC28J60 pins were configured in up_spi.c early in the boot-up phase.
+   * 2) Clocking for the SPI1 peripheral was also provided earlier in boot-up.
+   */
+
+  spi = stm32_spibus_initialize(ENC28J60_SPI_PORTNO);
+  if (!spi)
+    {
+      nerr("ERROR: Failed to initialize SPI port %d\n", ENC28J60_SPI_PORTNO);
+      return;
+    }
+
+  /* Bind the SPI port to the ENC28J60 driver */
+
+  ret = enc_initialize(spi, &g_enclower.lower, ENC28J60_DEVNO);
+  if (ret < 0)
+    {
+      nerr("ERROR: Failed to bind SPI port %d ENC28J60 device %d: %d\n",
+           ENC28J60_SPI_PORTNO, ENC28J60_DEVNO, ret);
+      return;
+    }
+
+  ninfo("Bound SPI port %d to ENC28J60 device %d\n",
+        ENC28J60_SPI_PORTNO, ENC28J60_DEVNO);
+#endif /* CONFIG_ENC28J60 */
 }
 #endif
 
diff --git a/configs/stm32f4discovery/src/stm32_spi.c b/configs/stm32f4discovery/src/stm32_spi.c
index a9be4c1d2d..0cafcc5aa0 100644
--- a/configs/stm32f4discovery/src/stm32_spi.c
+++ b/configs/stm32f4discovery/src/stm32_spi.c
@@ -70,7 +70,12 @@
 void weak_function stm32_spidev_initialize(void)
 {
 #ifdef CONFIG_STM32_SPI1
+# ifdef  CONFIG_ENC28J60
+  (void)stm32_configgpio(GPIO_ENC28J60_CS);  /* ENC28J60 chip select */
+  (void)stm32_configgpio(GPIO_ENC28J60_INTR);
+# else
   (void)stm32_configgpio(GPIO_CS_MEMS);    /* MEMS chip select */
+# endif
 #endif
 #if defined(CONFIG_STM32_SPI2) && defined(CONFIG_SENSORS_MAX31855)
   (void)stm32_configgpio(GPIO_MAX31855_CS); /* MAX31855 chip select */
@@ -126,6 +131,14 @@ void stm32_spi1select(FAR struct spi_dev_s *dev, uint32_t devid, bool selected)
 {
   spiinfo("devid: %d CS: %s\n", (int)devid, selected ? "assert" : "de-assert");
 
+#ifdef CONFIG_ENC28J60
+  if (devid == SPIDEV_ETHERNET(0))
+    {
+      /* Set the GPIO low to select and high to de-select */
+
+      stm32_gpiowrite(GPIO_ENC28J60_CS, !selected);
+    }
+#endif
 #ifdef CONFIG_LCD_ST7567
   if (devid == SPIDEV_DISPLAY(0))
     {
diff --git a/configs/stm32f4discovery/src/stm32f4discovery.h b/configs/stm32f4discovery/src/stm32f4discovery.h
index 80c269039c..c673ce1b76 100644
--- a/configs/stm32f4discovery/src/stm32f4discovery.h
+++ b/configs/stm32f4discovery/src/stm32f4discovery.h
@@ -286,6 +286,17 @@
 #define GPIO_CS_XEN1210   (GPIO_OUTPUT|GPIO_PUSHPULL|GPIO_SPEED_50MHz|\
                            GPIO_OUTPUT_SET|GPIO_PORTA|GPIO_PIN4)
 
+/* ENC28J60 Ethernet PHY */
+#ifdef CONFIG_ENC28J60
+/* PA1 */
+#  define GPIO_ENC28J60_CS    (GPIO_OUTPUT|GPIO_PUSHPULL|GPIO_SPEED_50MHz|\
+                               GPIO_OUTPUT_SET|GPIO_PORTA|GPIO_PIN4)
+/* PE4 */
+#  define GPIO_ENC28J60_INTR  (GPIO_INPUT|GPIO_FLOAT|GPIO_EXTI| \
+                               GPIO_OPENDRAIN|GPIO_PORTE|GPIO_PIN4)
+#endif
+
+
 /* USB OTG FS
  *
  * PA9  OTG_FS_VBUS VBUS sensing (also connected to the green LED)

コンフィグレーション

次のコンフィグレーションを全て有効化する。

CONFIG_NET

Networking Support
  -> Networking Support

CONFIG_NET_TCP

Networking Support
  -> Networking support
    -> TCP/IP Networking

CONFIG_NET_UDP

Networking Support
  -> Networking support
    -> UDP Networking

CONFIG_NET_BROADCAST

Networking Support
  -> Networking support
    -> UDP Networking
      -> Disable UDP/IP Stack
        -> UDP broadcast Rx support

CONFIG_NETDEV_PHY_IOCTL

これを忘れるとifup/ifdownコマンドが効かない

Networking Support
  -> Networking support
    -> Network Device Operations
      -> Enable PHY ioctl()

CONFIG_NET_ICMP

Networking Support
  -> Networking support
    -> ICMP Networking Support

CONFIG_NET_ICMP_SOCKET

Networking Support
  -> Networking support
    -> ICMP Networking Support
      -> IPPROTO_ICMP socket support

CONFIG_NETUTILS_PING

Application Configuration
  -> Network Utilities
    -> ICMP ping support

CONFIG_SYSTEM_PING

Application Configuration
  -> System Libraries and NSH Add-Ons
    -> ICMP 'ping' command

CONFIG_NSH_IPADDR

192.168.10.100に設定

Application Configuration
  -> NSH Library
    -> NSH Library
      -> Networking Configuration
        -> Network initialization
          -> IP Address Configuration
            -> Target IPv4 address

0xc0a80a64に設定

CONFIG_NSH_DRIPADDR

192.168.10.1に設定

Application Configuration
  -> NSH Library
    -> NSH Library
      -> Networking Configuration
        -> Network initialization
          -> IP Address Configuration
            -> Router IPv4 address

0xc0a80a01に設定

CONFIG_NSH_NOMAC

Application Configuration
  -> NSH Library
    -> NSH Library
      -> Networking Configuration
        -> Network initialization
          -> ardware has no MAC address

CONFIG_DISABLE_POLL=n

POLLの無効化を解除する。

Device Drivers
  -> Disable driver poll interfaces

CONFIG_NETDEVICES

Device Drivers
  -> Network Device/PHY Support

CONFIG_ENC28J60

Device Drivers
  -> Network Device/PHY Support
    -> ENC28J60

CONFIG_SCHED_HPWORK

RTOS Features
  -> Work queue support
    -> High priority (kernel) worker thread

CONFIG_SCHED_LPWORK

RTOS Features
  -> Work queue support
    -> Low priority (kernel) worker thread

コンフィグの作成

毎回これを手動で設定するのは面倒なのでコンフィグを作成する。

今回は予めnshを使用していることを想定して作業する。

defconfigの作成

$ make savedefconfig

stm32f4discovery/nsh_enc28j60の作成

これをconfigs/stm32f4discovery/nsh_enc28j60において、tools/configure.shで指定できるようにする。

$ mkdir -p configs/stm32f4discovery/nsh_enc28j60
$ cp defconfig configs/stm32f4discovery/nsh_enc28j60/

コンフィグの使用

作成したコンフィグ使う場合には次のようにする。

$ make distclean
$ tools/configure.sh -l stm32fdiscovery/nsh_enc28j60

ボードとの接続

ENC28J60 STM32F4DISCOVERY
5V 5V
LNT(INT) PE4
SO PA6
ST(SI) PA7
SCK PA5
CS PA4
GND GND

RSTなどいくつか使用していないピンがあるが、これで問題なく使用できる。

動作確認

対抗となるPCのIPアドレス192.168.10.1などに設定しpingを実行する。

次のようになればOK。

$ ping 192.168.10.100
PING 192.168.10.100 (192.168.10.100) 56(84) bytes of data.
64 bytes from 192.168.10.100: icmp_seq=2 ttl=64 time=1.00 ms
64 bytes from 192.168.10.100: icmp_seq=3 ttl=64 time=0.567 ms
64 bytes from 192.168.10.100: icmp_seq=4 ttl=64 time=0.596 ms

反対に、STM32F4Discovery側からは次のようになればOK。

nsh> ping 192.168.10.1
PING 192.168.10.1 56 bytes of data
56 bytes from 192.168.10.1: icmp_seq=0 time=0 ms
56 bytes from 192.168.10.1: icmp_seq=1 time=0 ms
56 bytes from 192.168.10.1: icmp_seq=2 time=0 ms
56 bytes from 192.168.10.1: icmp_seq=3 time=0 ms
56 bytes from 192.168.10.1: icmp_seq=4 time=0 ms
56 bytes from 192.168.10.1: icmp_seq=5 time=0 ms
56 bytes from 192.168.10.1: icmp_seq=6 time=0 ms
56 bytes from 192.168.10.1: icmp_seq=7 time=0 ms
56 bytes from 192.168.10.1: icmp_seq=8 time=0 ms
56 bytes from 192.168.10.1: icmp_seq=9 time=0 ms
10 packets transmitted, 10 received, 0% packet loss, time 10100 ms

リポジトリのクローン

この環境を組み込んだリポジトリのクローンをGithubに作成した。

使用する場合はnuttxのリポジトリとして次のものを使用する。

$ git clone https://github.com/mickey-happygolucky/nuttx.git -b enc28j60
$ git clone https://bitbucket.org/nuttx/apps.git
$ cd nuttx
$ tools/configure.sh -l configs/stm32f4discovery/nsh_enc28j60
$ make -j4
$ st-flash write nuttx.bin 0x8000000