みつきんのメモ

組み込みエンジニアです。Interface誌で「Yocto Projectではじめる 組み込みLinux開発入門」連載中

Ubuntu 18.04にtinygo-0.5.0をインストールする

はじめに

つい先日、Ubuntu 18.04でtinygoをインストールする手順を書いたが、 この短期間でLLVMが8になったようで、前回の記事の内容はほぼ使えなくなったので書き直す。

ここの手順が更新されており、 Ubuntuではビルド済みのdebパッケージが提供されているので 簡単に導入したい場合はこれを使用すると良い。

ここでは前回と同様にソースからインストールする。

Requirement

あとは、ターゲットのツールチェイン。

  • ARM Cortex-M
  • AVR(Arduino)
  • WebAssembly

詳細は先述のリンク元を参照。

筆者環境はarm-none-eabiがインストール済み。

goのインストール

いろいろ手段があるが、インストーラによるインストールが一番便利だった。

$ sudo apt install wget git
$ wget -q https://storage.googleapis.com/golang/getgo/installer_linux
$ chmod +x installer_linux
$ ./installer_linux -version 1.11.7

今回もgoのバージョンは1.11.7とした。

GOPATHの設定

.bashrcに以下の内容を追加。

export PATH=$PATH:/home/mickey/.go/bin
export GOPATH=/home/mickey/go
export PATH=$PATH:/home/mickey/go/bin

llvm-8のインストール

ここを参考にした。

sources.listの追加

前回はllvm 7.0.1をインストールするために、/etc/apt/sources.list.d/llvm.listを次の内容で作成したため、 llvm8を入れたい今回は不要かもしれないが一応やっておく。

# i386 not available
deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic main
deb-src http://apt.llvm.org/bionic/ llvm-toolchain-bionic main
# 7
deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-7 main
deb-src http://apt.llvm.org/bionic/ llvm-toolchain-bionic-7 main
# 8
deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-8 main
deb-src http://apt.llvm.org/bionic/ llvm-toolchain-bionic-8 main

鍵を追加。

$ wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
$ sudo apt update

パッケージのインストール

Install (stable branch)に書いてあったものをだいたい入れる。

$ sudo apt install clang-8 lldb-8 lld-8
$ sudo apt install libllvm-8-ocaml-dev libllvm8 llvm-8 llvm-8-dev llvm-8-doc llvm-8-examples llvm-8-runtime
$ sudo apt install  clang-8 clang-tools-8 clang-8-doc libclang-common-8-dev libclang-8-dev libclang1-8 clang-format-8 python-clang-8
$ sudo apt install libfuzzer-8-dev
$ sudo apt install lldb-8 lld-8 libc++-8-dev libc++abi-8-dev

tinygoのインストール

go getを実行する。今回はこれで問題なくインストールできる。

$ go get -u github.com/tinygo-org/tinygo

まとめ

最新版(0.5.0)ではdebパッケージが用意されたので、ソースからインストールしなくても簡単にインストールができる。

ソースコードからインストールする場合でも前回あったような、過渡期特有のワークアラウンドは必要なくなっている。

NuttXでmbedTLSを動かす

はじめに

NuttXでmbed TLSを動かす。

3年ほど前にMarceloさんが一度移植したらしいので、ここを参考にする。

ただし情報が古くその間にNuttXも新しくなっているのでこのままではうまく行かない。

今回使用するターゲットはSTM32F4Discovery。

NuttX

まずはNuttXのソースを取ってくる。

ディレクトリ構成

全体のディレクトリ構成が次のようになるようにする。

.
├── apps
├── mbedtls
└── nuttx

ソースの取得

$ git clone https://bitbucket.org/nuttx/nuttx.git
$ git clone https://bitbucket.org/nuttx/apps.git

mbedtls

ソースの取得

mbed TLSgithubでソースも公開しているのでこれを使う。

$ git clone https://github.com/ARMmbed/mbedtls.git

ファイルのコピー

mbed TLSのソースをNuttXのソースツリーに組み込む。

mbed TLSのツリー

mbed TLSのソースツリーは次のようになっている。

ここでは使用する分を抜粋する。

mbedtls
├── configs      // コンフィグヘッダ
├── include
│   └── mbedtls // ヘッダファイル
└── library      // ソースファイル

NuttX側

NuttXには次のようになるようにする。

nuttx
└── mbedtls           // ソースファイル
     └── mbedtls      // ヘッダファイル
          └── configs // コンフィグヘッダ

出力ディレクトリの作成

mbed TLSのソースディレクトリに移動し、出力先ディレクトリの作成。

$ cd mbedtls
$ mkdir ../nuttx/mbedtls

ソースファイルのコピー

インクルードファイルのコピー

$ cp -ra ./include/mbedtls ../nuttx/mbedtls/

コンフィグヘッダのコピー

configs以下のファイルから参照されることがあるためcheck_config.hconfigsにコピーする。 また、mbedtls/mbedtls/config.hはKconfigでの設定を反映できるように修正するため、もとのファイルをmbedtls/mbedtls/configs/config-default.hとしてコピーしておく。

$ cp -ra ./configs ../nuttx/mbedtls/mbedtls/
$ cp ./include/mbedtls/config.h ../nuttx/mbedtls/mbedtls/configs/config-default.h
$ cp ./include/mbedtls/check_config.h ../nuttx/mbedtls/mbedtls/configs/

ソースファイルのコピー

$ cp -ra ./library/*.c ../nuttx/mbedtls/

ライセンスとREADMEのコピー

$ cp -a ./LICENSE ../nuttx/mbedtls
$ cp -a ./apache-2.0.txt ../nuttx/mbedtls
$ cp -a ./README.md ../nuttx/mbedtls

ビルドに必要なファイルの作成

nuttx以下に次のファイルを作成する。

  • mbedtls/Kconfig
  • mbedtls/Makefile
  • mbedtls/mbedtls/config.h

Kconfig

mbedtls/Kconfigを次の内容で作成する。

config MBED_TLS_LIB
    bool "mbed TLS Support"
    default n
    ---help---
        Enable or disable mbed TLS support

if MBED_TLS_LIB

choice
    prompt "mbed Configuration Profile"
    default MBED_PROFILE_DEFAULT

config MBED_PROFILE_SUITE_B
    bool "Suite B"

config MBED_PROFILE_CCM_PSK_TLS_1_2
    bool "CCM, PSK and TLS 1.2"

config MBED_PROFILE_MINI_TLS_1_1
    bool "Mini TLS 1.1"

config MBED_PROFILE_THREAD
    bool "Thread"

config MBED_PROFILE_NOENTROPY
    bool "No entropy"

config MBED_PROFILE_DEFAULT
    bool "Default configuration"

endchoice

endif # MBED_TLS_LIB

Makefile

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

-include $(TOPDIR)/Make.defs

ASRCS =
CSRCS =

ifeq ($(CONFIG_MBED_TLS_LIB),y)

CSRCS += aes.c          asn1parse.c asn1write.c             base64.c        \
        bignum.c        blowfish.c              camellia.c      \
        ccm.c           cipher.c                cipher_wrap.c   \
        ctr_drbg.c      des.c                   dhm.c           \
        ecdh.c          ecdsa.c                 ecjpake.c       \
        ecp.c           ecp_curves.c            entropy.c       \
        entropy_poll.c  error.c                 gcm.c           \
        havege.c        hmac_drbg.c             md.c            \
        md2.c           md4.c                   md5.c           \
        md_wrap.c       memory_buffer_alloc.c   oid.c           \
        padlock.c       pem.c                   pk.c            \
        pk_wrap.c       pkcs12.c                pkcs5.c         \
        pkparse.c       pkwrite.c               platform.c      \
        ripemd160.c     rsa.c                   sha1.c          \
        sha256.c        sha512.c                threading.c     \
        timing.c        version_features.c      version.c       \
        xtea.c          x509_create.c           x509_crl.c      \
        x509_crt.c      x509_csr.c              x509write_crt.c \
        x509write_csr.c ssl_ciphersuites.c      ssl_cli.c       \
        ssl_cookie.c    ssl_srv.c               ssl_ticket.c    \
        ssl_tls.c       platform_util.c


endif # CONFIG_MBED_TLS_LIB

AOBJS = $(ASRCS:.S=$(OBJEXT))
COBJS = $(CSRCS:.c=$(OBJEXT))

SRCS = $(ASRCS) $(CSRCS)
OBJS = $(AOBJS) $(COBJS)

BIN = libmbedtls$(LIBEXT)

all: $(BIN)
.PHONY: depend clean distclean

$(AOBJS): %$(OBJEXT): %.S
    $(call ASSEMBLE, $<, $@)

$(COBJS): %$(OBJEXT): %.c
    $(call COMPILE, $<, $@)

$(BIN): $(OBJS)
    $(call ARCHIVE, $@, $(OBJS))

.depend: Makefile $(SRCS)
ifeq ($(CONFIG_MBED_TLS_LIB),y)
    $(Q) $(MKDEP) --dep-path . "$(CC)" -- $(CFLAGS) -- $(SRCS) >Make.dep
endif
    $(Q) touch $@

depend: .depend

clean:
    $(call DELFILE, $(BIN))
    $(call CLEAN)

distclean: clean
    $(call DELFILE, Make.dep)
    $(call DELFILE, .depend)

-include Make.dep

config.h

Kconfigの内容を反映できるようにするために、既存のmbedtls/mbedtls/config.hを破棄し、次の内容で作成する。

#include <nuttx/config.h>

#if defined(CONFIG_MBED_PROFILE_SUITE_B)
#include "mbedtls/configs/config-suite-b.h"
#elif defined(CONFIG_MBED_PROFILE_CCM_PSK_TLS_1_2)
#include "mbedtls/configs/config-ccm-psk-tls1_2.h"
#elif defined(CONFIG_MBED_PROFILE_MINI_TLS_1_1)
#include "mbedtls/configs/config-mini-tls1_1.h"
#elif defined(CONFIG_MBED_PROFILE_THREAD)
#include "mbedtls/configs/config-thread.h"
#elif defined(CONFIG_MBED_PROFILE_NOENTROPY)
#include "mbedtls/configs/config-no-entropy.h"
#elif defined(CONFIG_MBED_PROFILE_DEFAULT)
#include "mbedtls/configs/config-default.h"
#else
#error "mbed TLS configuration file was not set"
#endif

config-default.h

先程コピーしたmbedtls/mbedtls/configs/config-default.hは、このままではビルドに失敗するため、次の2つのコンフィグを変更しておく。

diff --git a/mbedtls/mbedtls/configs/config-default.h b/mbedtls/mbedtls/configs/config-default.h
index fd91d7074c..878f4e67cf 100644
--- a/mbedtls/mbedtls/configs/config-default.h
+++ b/mbedtls/mbedtls/configs/config-default.h
@@ -1149,7 +1149,7 @@
  *
  * Uncomment this macro to disable the built-in platform entropy functions.
  */
-//#define MBEDTLS_NO_PLATFORM_ENTROPY
+#define MBEDTLS_NO_PLATFORM_ENTROPY
 
 /**
  * \def MBEDTLS_ENTROPY_FORCE_SHA256
@@ -3018,7 +3018,7 @@
  *
  * This module is used by the HAVEGE random number generator.
  */
-#define MBEDTLS_TIMING_C
+//#define MBEDTLS_TIMING_C
 
 /**
  * \def MBEDTLS_VERSION_C

既存ファイルの修正

ビルド対象に含めるためにNuttX側のファイルを修正する。

今回は次のファイルを修正する。

  • Kconfig
  • tools/Directories.mk
  • tools/FlatLibs.mk
  • tools/KernelLibs.mk
  • tools/LibTargets.mk
  • tools/ProtectedLibs.mk

Kconfig

NuttXのソースツリーのルートにあるKconfigを次のように修正する。

diff --git a/Kconfig b/Kconfig
index dbcf9ee9ff..1ea6383698 100644
--- a/Kconfig
+++ b/Kconfig
@@ -1698,6 +1698,10 @@ source libs/libxx/Kconfig
 source libs/libdsp/Kconfig
 endmenu
 
+menu "mbed TLS Support"
+source mbedtls/Kconfig
+endmenu
+
 menu "Application Configuration"
 source "$APPSDIR/Kconfig"
 endmenu

Directories.mk

tools/Directories.mkを次のように修正する。

diff --git a/tools/Directories.mk b/tools/Directories.mk
index 7c17510789..8b66691dfa 100644
--- a/tools/Directories.mk
+++ b/tools/Directories.mk
@@ -183,3 +183,8 @@ ifeq ($(CONFIG_CRYPTO),y)
 KERNDEPDIRS += crypto
 endif
 CLEANDIRS += crypto
+
+ifeq ($(CONFIG_MBED_TLS_LIB),y)
+KERNDEPDIRS += mbedtls
+endif
+CLEANDIRS += mbedtls

FlatLibs.mk

tools/FlatLibs.mkを次のように修正する。

diff --git a/tools/FlatLibs.mk b/tools/FlatLibs.mk
index 0ad2cc335b..fde3dd8c6e 100644
--- a/tools/FlatLibs.mk
+++ b/tools/FlatLibs.mk
@@ -93,6 +93,12 @@ ifeq ($(CONFIG_NET),y)
 NUTTXLIBS += staging$(DELIM)libnet$(LIBEXT)
 endif
 
+# Add libraries for mbed TLS
+
+ifeq ($(CONFIG_MBED_TLS_LIB),y)
+NUTTXLIBS += staging$(DELIM)libmbedtls$(LIBEXT)
+endif
+
 # Add libraries for Crypto API support
 
 ifeq ($(CONFIG_CRYPTO),y)

KernelLibs.mk

tools/KernelLibs.mkを次のように修正する。

diff --git a/tools/KernelLibs.mk b/tools/KernelLibs.mk
index f36d8d6ce5..1f14159d87 100644
--- a/tools/KernelLibs.mk
+++ b/tools/KernelLibs.mk
@@ -91,6 +91,12 @@ ifeq ($(CONFIG_CRYPTO),y)
 NUTTXLIBS += staging$(DELIM)libcrypto$(LIBEXT)
 endif
 
+# Add libraries for mbed TLS
+
+ifeq ($(CONFIG_MBED_TLS_LIB),y)
+NUTTXLIBS += staging$(DELIM)libmbedtls$(LIBEXT)
+endif
+
 # Add libraries for file system support
 
 NUTTXLIBS += staging$(DELIM)libfs$(LIBEXT) staging$(DELIM)libbinfmt$(LIBEXT)

LibTargets.mk

tools/LibTargets.mkを次のように修正する。

diff --git a/tools/LibTargets.mk b/tools/LibTargets.mk
index 188e4fb403..df5421ae89 100644
--- a/tools/LibTargets.mk
+++ b/tools/LibTargets.mk
@@ -94,6 +94,12 @@ crypto$(DELIM)libcrypto$(LIBEXT): context
 staging$(DELIM)libcrypto$(LIBEXT): crypto$(DELIM)libcrypto$(LIBEXT)
        $(Q) $(call INSTALL_LIB,$<,$@)
 
+mbedtls$(DELIM)libmbedtls$(LIBEXT): context
+       $(Q) $(MAKE) -C mbedtls TOPDIR="$(TOPDIR)" libmbedtls$(LIBEXT) KERNEL=y EXTRADEFINES=$(KDEFINE)
+
+staging$(DELIM)libmbedtls$(LIBEXT): mbedtls$(DELIM)libmbedtls$(LIBEXT)
+       $(Q) $(call INSTALL_LIB,$<,$@)
+
 fs$(DELIM)libfs$(LIBEXT): context
        $(Q) $(MAKE) -C fs TOPDIR="$(TOPDIR)" libfs$(LIBEXT) KERNEL=y EXTRADEFINES=$(KDEFINE)
 

インデントはスペースではなくタブであることに注意すること。

ProtectedLibs.mk

tools/ProtectedLibs.mkを次のように修正する。

diff --git a/tools/ProtectedLibs.mk b/tools/ProtectedLibs.mk
index 4bb3d19424..8ed6e3e461 100644
--- a/tools/ProtectedLibs.mk
+++ b/tools/ProtectedLibs.mk
@@ -98,6 +98,12 @@ ifeq ($(CONFIG_CRYPTO),y)
 NUTTXLIBS += staging$(DELIM)libcrypto$(LIBEXT)
 endif
 
+# Add libraries for mbed TLS
+
+ifeq ($(CONFIG_MBED_TLS_LIB),y)
+NUTTXLIBS += staging$(DELIM)libmbedtls$(LIBEXT)
+endif
+
 # Add libraries for file system support
 
 NUTTXLIBS += staging$(DELIM)libfs$(LIBEXT) staging$(DELIM)libbinfmt$(LIBEXT)

サンプルプログラム

簡単に動作を確認するためMD5を動かしてみる。

プログラムはtlssampleとする。

新規に作成するアプリケーションのために、次のファイルをapps以下に追加する。

  • external/tlssample/Kconfig
  • external/tlssample/Make.defs
  • external/tlssample/Makefile
  • external/tlssample/tlssample_main.c

ディレクトリ作成

次のコマンドでプログラムのためのディレクトリを作成する。

$ cd apps
$ mkdir -p external/tlssample
$ ln -s external/tlssample tlssample

appsから1つ下の階層のディレクトリにあるMakefileは自動的にビルド対象に追加されるため、 external/tlssampleへのシンボリックリンクを作成する。

Kconfig

external/tlssample/Kconfigを次の内容で作成する。

config EXTERNAL_TLSSAMPLE
    tristate "\"Tlssample\" example"
    default n
    ---help---
        Enable the \"Tlssample\" example

if EXTERNAL_TLSSAMPLE

config EXTERNAL_TLSSAMPLE_PROGNAME
    string "Program name"
    default "tlssample"
    depends on BUILD_LOADABLE
    ---help---
        This is the name of the program that will be use when the NSH ELF
        program is installed.

config EXTERNAL_TLSSAMPLE_PRIORITY
    int "Tlssample task priority"
    default 100

config EXTERNAL_TLSSAMPLE_STACKSIZE
    int "Tlssample stack size"
    default 2048

endif

Make.defs

external/tlssample/Make.defsを次の内容で作成する。

ifneq ($(CONFIG_EXTERNAL_TLSSAMPLE),)
CONFIGURED_APPS += tlssample
endif

Makefile

external/tlssample/Makefileを次の内容で作成する。

-include $(TOPDIR)/Make.defs

# Tlssample, World! built-in application info

CONFIG_EXTERNAL_TLSSAMPLE_PRIORITY ?= SCHED_PRIORITY_DEFAULT
CONFIG_EXTERNAL_TLSSAMPLE_STACKSIZE ?= 2048

APPNAME = tlssample

PRIORITY  = $(CONFIG_EXTERNAL_TLSSAMPLE_PRIORITY)
STACKSIZE = $(CONFIG_EXTERNAL_TLSSAMPLE_STACKSIZE)

# Tlssample, World! Example

ASRCS =
CSRCS =
MAINSRC = tlssample_main.c

CFLAGS += -I$(TOPDIR)/mbedtls

CONFIG_EXTERNAL_TLSSAMPLE_PROGNAME ?= tlssample$(EXEEXT)
PROGNAME = $(CONFIG_EXTERNAL_TLSSAMPLE_PROGNAME)

MODULE = CONFIG_EXTERNAL_TLSSAMPLE

include $(APPDIR)/Application.mk

tlssample_main.c

external/tlssample/tlssample_main.cを次の内容で作成する。

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>
#include <stdio.h>
#include "mbedtls/md5.h"

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * tlssample_main
 ****************************************************************************/

#if defined(BUILD_MODULE)
int main(int argc, FAR char *argv[])
#else
int tlssample_main(int argc, char *argv[])
#endif
{
  unsigned char buf[32] = "hello world";
  unsigned char out[16];
  int i = 0;
  int n;


  n = strlen(buf);
  printf("source = %s, %d\n", buf, n);
  mbedtls_md5_ret(buf, n, out);
  printf("-----\n");
  for (i = 0; i < 16; ++i) {
    printf("%02x ", out[i]);
  }

  printf("\n-----\n");
  mbedtls_md5_self_test(1);

  return 0;
}

ビルド

ここまででmbedtlsを含んだNuttXのビルドは可能となった。

STM32F4Discoveryのusbnsh環境向けにビルドしてみる。

$ tools/configure.sh configs/stm32f4discovery/usbnsh -l
$ make menuconfig

mbed TLSの有効化

次のような感じで設定する。

f:id:mickey_happygolucky:20190423230242p:plain

f:id:mickey_happygolucky:20190423230302p:plain

f:id:mickey_happygolucky:20190423230314p:plain

現時点ではDefault configurationNo entropy以外では次のチェックで引っかかる。

#if !defined(MBEDTLS_NO_PLATFORM_ENTROPY)

#if !defined(unix) && !defined(__unix__) && !defined(__unix) && \
    !defined(__APPLE__) && !defined(_WIN32) && !defined(__QNXNTO__) && \
    !defined(__HAIKU__)
#error "Platform entropy sources only work on Unix and Windows, see MBEDTLS_NO_PLATFORM_ENTROPY in config.h"
#endif

Linnux、Mac OSQNXHAIKUWindows以外のOSでは機能が足りないと判断されるためエラーとなる。

今回は試していないが、NuttXはPOSIX LikeなOSなのでここのチェックを騙して、 ちょっと修正すれば他のコンフィグレーションでも動作するようになると思われる。

サンプルプログラムの有効化

次のような感じで設定する。

f:id:mickey_happygolucky:20190423230327p:plain

f:id:mickey_happygolucky:20190423230402p:plain

実行

プログラムの書き込み

st-flashでnuttx.binを書き込む。

$ st-flash write nuttx.bin 0x8000000

プログラムの実行

STM32F4DiscoveryとPCをマイクロUSBケーブルで接続しボードをリセットすると、PC側に/dev/ttyACM0が見えるようになる。

このデバイスをminicomなどで開いてnshにアクセスする。

nsh> tlssample
source = hello world, 11
-----
5e b6 3b bb e0 1e ee d0 93 cb 22 bb 8f 5a cd c3 
-----
  MD5 test #1: passed
  MD5 test #2: passed
  MD5 test #3: passed
  MD5 test #4: passed
  MD5 test #5: passed
  MD5 test #6: passed
  MD5 test #7: passed

nsh> 

hello worldMD55e b6 3b bb e0 1e ee d0 93 cb 22 bb 8f 5a cd c3となる。

確認方法

Linuxで次のコマンドを実行し同じになることを確認する。

$ echo -n "hello world" | md5sum
5eb63bbbe01eeed093cb22bb8f5acdc3

同じ結果になった。

まとめ

既存のライブラリをNuttXのソースツリーに持っていきビルドする方法を確認した。 サンプルプログラムを作成し、きちんと機能することも確認できた。

以前にmbed TLSを移植したMarceloさんは各コンフィグをそれぞれ選択できるようにカスタムメニューを作成しているが、 今回は省略した。

今のところNuttXは割と素直に動いてくれていて、 Linux経験者がMCUに世界に飛び込むにはかなりとっつきやすい印象。

ただ、移植にはライブラリが求めている機能をNuttXが持っているかどうかなど条件があり、 これを満たすようにするためには、いろいろと修正しなければならない場合がある。

NuttXでOSSのライブラリを使用するには、事前にそのライブラリの移植性やその難易度を予め調査するのが良さそう。 以前にNuttXに移植された実績があるかどうかなども、結構重要なポイントとなる。

規模の大きいものや複雑なものを使用したい場合は注意が必要。

ただ、NuttXは楽しい。

npm install -g mermaid-filterでエラー

はじめに

pandocの環境を再構築するために、mermaid-filterをインストールしようとしたら次のエラーが出てハマった。

$ sudo npm install -g mermaid-filter
/usr/local/bin/mermaid-filter -> /usr/local/lib/node_modules/mermaid-filter/index.js

> puppeteer@1.14.0 install /usr/local/lib/node_modules/mermaid-filter/node_modules/puppeteer
> node install.js

ERROR: Failed to download Chromium r641577! Set "PUPPETEER_SKIP_CHROMIUM_DOWNLOAD" env variable to skip download.
{ Error: EACCES: permission denied, mkdir '/usr/local/lib/node_modules/mermaid-filter/node_modules/puppeteer/.local-chromium'
  -- ASYNC --
    at BrowserFetcher.<anonymous> (/usr/local/lib/node_modules/mermaid-filter/node_modules/puppeteer/lib/helper.js:110:27)
    at Object.<anonymous> (/usr/local/lib/node_modules/mermaid-filter/node_modules/puppeteer/install.js:64:16)
    at Module._compile (internal/modules/cjs/loader.js:701:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:712:10)
    at Module.load (internal/modules/cjs/loader.js:600:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:539:12)
    at Function.Module._load (internal/modules/cjs/loader.js:531:3)
    at Function.Module.runMain (internal/modules/cjs/loader.js:754:12)
    at startup (internal/bootstrap/node.js:283:19)
    at bootstrapNodeJSCore (internal/bootstrap/node.js:622:3)
  errno: -13,
  code: 'EACCES',
  syscall: 'mkdir',
  path:
   '/usr/local/lib/node_modules/mermaid-filter/node_modules/puppeteer/.local-chromium' }
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! puppeteer@1.14.0 install: `node install.js`
npm ERR! Exit status 1
npm ERR! 
npm ERR! Failed at the puppeteer@1.14.0 install script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

解決方法

どうもchromiumブラウザをダンロードしてこようとしているようだが本来は特に必要ない。 ダンロードをスキップする場合は次の環境変数を設定しろというメッセージが出た。

Set "PUPPETEER_SKIP_CHROMIUM_DOWNLOAD" env variable to skip download.

いろいろ調べて次のようにしたが状況は変わらない。

$ npm config set PUPPETEER_SKIP_CHROMIUM_DOWNLOAD TRUE -g

ここの情報をもとに次のようにしたところ、エラーが回避できた。

$ sudo npm config set puppeteer_skip_chromium_download true -g

小文字なんだってさ。

yoctoでjetson nanoを動かす

はじめに

meta-tegrajetson nanoに対応したので、yoctoで動かしてみる。

いくつか注意点

readmeに重要な注意点がいくつか書いてある。

  • jetpackは必要
  • cuda10を使用する関係でgccは7系固定

Jetson-TX1についても書いてあるけど関係ないので無視。

そして最後に重要なやつ。

Toolchain version selection is usually a distro configuration setting,
but you can also set this in your build/conf/local.conf file. To use
gcc 7 instead of gcc 8, set:

GCCVERSION = "7.%"

but you will also need the gcc 7 toolchain recipes in one of your layers,
since it was retired from OE-Core in favor of gcc 8.

local.confでGCCVERSION = "7.%"と設定すればgcc7系を選択しようとするけど、 warriorブランチではgcc7系のレシピは削除されているので、自分でレイヤ作ってgcc7系のレシピを用意してね!

とのこと。

JetPackのインストール

CUDAを使用するためにはNVIDIAプロプライエタリな成果物を必要とするため yocto環境だけでは完結しない。

予めNVIDIA Developer Network(Devnet)にログインしJetPackのパッケージをインストールしておく必要がある。 このためにはDevnetのアカウントを作成する必要がある。

NVIDIAのサイトJOINをクリックし、指示に従ってアカウントを作成する。

アカウントの作成ができたら、ここからNVIDIA SDK Managerをダウンロードする。

debパッケージなのでgdebiコマンドでインストールと便利。

$ sudo gdebi sdkmanager_0.9.11-3405_amd64.deb

SDK Managerを起動する。

$ sdkmanager

ログイン画面が表示されるのでDevnetのアカウントでログインする。

f:id:mickey_happygolucky:20190421002434p:plain

Target HardwareをJetson Nanoに変更する。

f:id:mickey_happygolucky:20190421002442p:plain

I accept the terms and conditions of the license agreementsにチェックを入れる。

f:id:mickey_happygolucky:20190421002458p:plain

Linuxの管理者権限のパスワード(sudo)を入力する。 ログインパスワードではないので注意。

f:id:mickey_happygolucky:20190421002517p:plain

セットアップの項目が出てくるので、全部スキップする。

f:id:mickey_happygolucky:20190421002522p:plain f:id:mickey_happygolucky:20190421002532p:plain

本当にスキップするか確認されるのでYesをクリック

f:id:mickey_happygolucky:20190421002538p:plain

完了。

f:id:mickey_happygolucky:20190421002551p:plain

環境構築

ソース取得

Jetson Nanoはmeta-tegraのmasterかwarriorブランチでのみ対応している。 masterブランチのLAYERSERIES_COMPATwarriorに設定されているので、 thud以前のバージョンでは使用できない。

warriorは開発中のステータスだが、今回はこれを使う。

$ mkdir -p jetson-warrior/layers
$ cd jetson-warrior/layers
$ git clone git://git.yoctoproject.org/poky.git -b warrior
$ git clone git://git.openembedded.org/meta-openembedded
$ git clone https://github.com/madisongh/meta-tegra.git -b warrior
$ cd ../

meta-openembeddedwarriorブランチがまだ無いので、masterブランチを使用する。

環境変数設定

$ source layers/poky/oe-init-build-env build

自動的にビルドディレクトリに移動される。 これで、bitbake関連のツールが使用可能になる。

レイヤ追加

$ bitbake-layers add-layer ../layers/meta-openembedded/meta-oe
$ bitbake-layers add-layer ../layers/meta-tegra/

meta-localの作成

先述の通りgcc7系のレシピを用意する必要があるので、そのためのレイヤを作成する。

$ bitbake-layers create-layer meta-local
$ rm -rf ./meta-local/recipes-example
$ mkdir -p ./meta-local/recipes-devtools/gcc
$ mv meta-local ../layers
$ bitbake-layers add-layer ../layers/meta-local

pokyリポジトリから古いレシピを取り出す。

$ pushd .
$ cd ../layers/poky/meta/recipes-devtools/gcc
$ git checkout -b thud origin/thud
$ cp -ra *7.3* ../../../../meta-local/recipes-devtools/gcc/
$ cp *.inc ../../../../meta-local/recipes-devtools/gcc/
$ rm -f ../../../../meta-local/recipes-devtools/gcc/*8.*
$ git checkout warrior
$ git branch -D thud
$ popd

gccレシピの修正

warriorではgcc-initialが廃止されるなど、glibcとの依存関係が変更されているためレシピを修正する必要がある。

diff --git a/recipes-devtools/gcc/gcc-cross-canadian.inc b/recipes-devtools/gcc/gcc-cross-canadian.inc
index 2f32d34..e7c08d3 100644
--- a/recipes-devtools/gcc/gcc-cross-canadian.inc
+++ b/recipes-devtools/gcc/gcc-cross-canadian.inc
@@ -3,7 +3,7 @@ inherit cross-canadian
 SUMMARY = "GNU cc and gcc C compilers (cross-canadian for ${TARGET_ARCH} target)"
 PN = "gcc-cross-canadian-${TRANSLATED_TARGET_ARCH}"
 
-DEPENDS = "virtual/${TARGET_PREFIX}gcc virtual/${HOST_PREFIX}gcc-crosssdk virtual/${HOST_PREFIX}binutils-crosssdk virtual/nativesdk-${HOST_PREFIX}libc-for-gcc nativesdk-gettext flex-native"
+DEPENDS = "virtual/${TARGET_PREFIX}gcc virtual/${HOST_PREFIX}gcc-crosssdk virtual/${HOST_PREFIX}binutils-crosssdk virtual/nativesdk-libc nativesdk-gettext flex-native virtual/libc"
 
 GCCMULTILIB = "--enable-multilib"
 
diff --git a/recipes-devtools/gcc/gcc-cross.inc b/recipes-devtools/gcc/gcc-cross.inc
index 89b1968..3274d08 100644
--- a/recipes-devtools/gcc/gcc-cross.inc
+++ b/recipes-devtools/gcc/gcc-cross.inc
@@ -2,7 +2,7 @@ inherit cross
 
 INHIBIT_DEFAULT_DEPS = "1"
 EXTRADEPENDS = ""
-DEPENDS = "virtual/${TARGET_PREFIX}binutils virtual/${TARGET_PREFIX}libc-for-gcc ${EXTRADEPENDS} ${NATIVEDEPS}"
+DEPENDS = "virtual/${TARGET_PREFIX}binutils ${EXTRADEPENDS} ${NATIVEDEPS}"
 PROVIDES = "virtual/${TARGET_PREFIX}gcc virtual/${TARGET_PREFIX}g++"
 python () {
     if d.getVar("TARGET_OS").startswith("linux"):
@@ -31,7 +31,6 @@ EXTRA_OECONF += "\
     --with-system-zlib \
 "
 
-DEPENDS_remove_libc-baremetal := "virtual/${TARGET_PREFIX}libc-for-gcc"
 EXTRA_OECONF_append_libc-baremetal = " --without-headers"
 EXTRA_OECONF_remove_libc-baremetal = "--enable-threads=posix"
 EXTRA_OECONF_remove_libc-newlib = "--enable-threads=posix"
@@ -44,6 +43,11 @@ EXTRA_OECONF_PATHS = "\
 
 ARCH_FLAGS_FOR_TARGET += "-isystem${STAGING_DIR_TARGET}${target_includedir}"
 
+do_configure_prepend () {
+   install -d ${RECIPE_SYSROOT}${target_includedir}
+   touch ${RECIPE_SYSROOT}${target_includedir}/limits.h
+}
+
 do_compile () {
    export CC="${BUILD_CC}"
    export AR_FOR_TARGET="${TARGET_SYS}-ar"
diff --git a/recipes-devtools/gcc/gcc-crosssdk.inc b/recipes-devtools/gcc/gcc-crosssdk.inc
index cda2927..bd2e71d 100644
--- a/recipes-devtools/gcc/gcc-crosssdk.inc
+++ b/recipes-devtools/gcc/gcc-crosssdk.inc
@@ -8,5 +8,5 @@ SYSTEMLIBS1 = "${SDKPATHNATIVE}${libdir_nativesdk}/"
 
 GCCMULTILIB = "--disable-multilib"
 
-DEPENDS = "virtual/${TARGET_PREFIX}binutils-crosssdk virtual/nativesdk-${TARGET_PREFIX}libc-for-gcc gettext-native ${NATIVEDEPS}"
+DEPENDS = "virtual/${TARGET_PREFIX}binutils-crosssdk gettext-native ${NATIVEDEPS}"
 PROVIDES = "virtual/${TARGET_PREFIX}gcc-crosssdk virtual/${TARGET_PREFIX}g++-crosssdk"
diff --git a/recipes-devtools/gcc/libgcc-initial.inc b/recipes-devtools/gcc/libgcc-initial.inc
index 950ad86..6edbd30 100644
--- a/recipes-devtools/gcc/libgcc-initial.inc
+++ b/recipes-devtools/gcc/libgcc-initial.inc
@@ -1,20 +1,58 @@
+#
+# Notes on the way the OE cross toolchain now works
+#
+# We need a libgcc to build glibc. Tranditionally we therefore built
+# a non-threaded and non-shared compiler (gcc-cross-initial), then use
+# that to build libgcc-initial which is used to build glibc which we can
+# then build gcc-cross and libgcc against.
+#
+# We were able to drop the glibc dependency from gcc-cross, with two tweaks:
+
+# a) specify the minimum glibc version to support in a configure option
+# b) create a dummy limits.h file so that later when glibc creates one,
+#    the headers structure has support for it. We can do this with a simple
+#    empty file
+#
+# Once gcc-cross is libc independent, we can use it to build both
+# libgcc-initial and then later libgcc.
+#
+# libgcc-initial is tricky as we need to imitate the non-threaded and
+# non-shared case. We can do that by hacking the threading mode back to
+# "single" even if gcc reports "posix" and disable libc presence for the
+# libgcc-intial build. We have to create the dummy limits.h to avoid
+# compiler errors from a missing header.
+#
+# glibc will fail to link with libgcc-initial due to a missing "exception
+# handler" capable libgcc (libgcc_eh.a). Since we know glibc doesn't need
+# any exception handler, we can safely symlink to libgcc.a.
+#
+
 require libgcc-common.inc
 
-DEPENDS = "virtual/${TARGET_PREFIX}gcc-initial"
+DEPENDS = "virtual/${TARGET_PREFIX}gcc"
 
 LICENSE = "GPL-3.0-with-GCC-exception"
 
-STAGINGCC = "gcc-cross-initial-${TARGET_ARCH}"
-STAGINGCC_class-nativesdk = "gcc-crosssdk-initial-${SDK_SYS}"
-PATH_prepend = "${STAGING_BINDIR_TOOLCHAIN}.${STAGINGCC}:"
-
 PACKAGES = ""
 
 EXTRA_OECONF += "--disable-shared"
 
-COMPILERINITIAL = "-initial"
-
 inherit nopackages
 
 # We really only want this built by things that need it, not any recrdeptask
 deltask do_build
+
+do_configure_prepend () {
+   install -d ${STAGING_INCDIR}
+   touch ${STAGING_INCDIR}/limits.h
+   sed -i -e 's#INHIBIT_LIBC_CFLAGS =.*#INHIBIT_LIBC_CFLAGS = -Dinhibit_libc#' ${B}/gcc/libgcc.mvars
+   sed -i -e 's#inhibit_libc = false#inhibit_libc = true#' ${B}/gcc/Makefile
+}
+
+do_configure_append () {
+   sed -i -e 's#thread_header = .*#thread_header = gthr-single.h#' ${B}/${BPN}/Makefile
+}
+
+do_install_append () {
+   ln -s libgcc.a ${D}${libdir}/${TARGET_SYS}/${BINV}/libgcc_eh.a
+}
\ No newline at end of file
diff --git a/recipes-devtools/gcc/libgcc.inc b/recipes-devtools/gcc/libgcc.inc
index 5f1dff6..e4e0c48 100644
--- a/recipes-devtools/gcc/libgcc.inc
+++ b/recipes-devtools/gcc/libgcc.inc
@@ -1,6 +1,6 @@
 require libgcc-common.inc
 
-DEPENDS = "virtual/${TARGET_PREFIX}gcc virtual/${TARGET_PREFIX}g++"
+DEPENDS = "virtual/${TARGET_PREFIX}gcc virtual/${TARGET_PREFIX}g++ virtual/${MLPREFIX}libc"
 
 do_install_append_class-target () {
    if [ "${TCLIBC}" != "glibc" ]; then

次の2つの修正を参考にした。

https://git.yoctoproject.org/cgit/cgit.cgi/poky/commit/meta/recipes-devtools/gcc?id=0afd3ac3ada35dd986aaf3be41d7177dc6b71ade https://git.yoctoproject.org/cgit/cgit.cgi/poky/commit/meta/recipes-devtools/gcc?id=1867965acd5291725069346eda9b239e60af2694

libxcryptのエラー回避

missing-attributesのオプションが原因でエラーになるのでbbappendを追加して回避する。

$ recipetool newappend -e ../layers/meta-local libxcrypt

このコマンドでエディタが起動するので次の内容を記述する。

TARGET_CPPFLAGS_remove = "-Wno-error=missing-attributes"
CPPFLAGS_remove_class-nativesdk = " -Wno-error=missing-attributes"

local.confの修正

色々と注意する点がある。

MACHINEの設定

MACHINE = "jetson-nano"

NVIDIA_DEVNET_MIRRORの設定

sdkmanagerがダウンロードしたファイルを格納しているディレクトリをNVIDIA_DEVNET_MIRRORで指定する必要がある。 筆者の環境では~/Downloads/nvidia/sdkm_downloadsになっていた。

NVIDIA_DEVNET_MIRROR = "file://${HOME}/Downloads/nvidia/sdkm_downloads"

gcc7を選択

CUDA10ではgccのバージョンが7で固定されているらしいのでGCCVERSION = '7.%'が必要。

GCCVERSION = "7.%"

DEBUG_PREFIX_MAPの変更

gcc7を使用する場合EXTRA_OECONF変数の中にmacro-prefix-mapがあると、libgccのdo_configureでエラーがになるため回避。

DEBUG_PREFIX_MAP = " -fdebug-prefix-map=${WORKDIR}=/usr/src/debug/${PN}/${EXTENDPE}${PV}-${PR} \
                     -fdebug-prefix-map=${STAGING_DIR_HOST}= \
                     -fdebug-prefix-map=${STAGING_DIR_NATIVE}= \
"

パッケージシステムの変更

デフォルトではrpmを使用するようになっているが、NVIDIAが提供するプロプライエタリなパッケージがdeb形式なので、 PACKAGE_CLASSESを変更する。

PACKAGE_CLASSES = "package_deb"

SDカードイメージ出力の設定

SDカードに直接ddで書き込めるイメージを生成するために次の内容を追加する。

IMAGE_CLASSES += "image_types_tegra"
IMAGE_FSTYPES = "tegraflash"

SDカードイメージのサイズ変更(オプション)

これはやらなくてもいいが、デフォルトでは16Gのイメージが生成される。

容量が大きめのSDカードを用意する必要があること、書き込みに時間がかかることから、 OSを頻繁に作り直す場合は不便となる。

TEGRAFLASH_SDCARD_SIZEで変更できる。

TEGRAFLASH_SDCARD_SIZE = "3G"

数字の部分は整数のみ。3.5Gなどの表記はできないのでそのようなサイズを指定したい場合は3500Mなどとする。

cuda関連パッケージ

cuda関連のパッケージを追加する。

# cuda
IMAGE_INSTALL_append = " \
             cuda-toolkit \
             cudnn \
             tegra-tools \
             opencv \
"

サンプルパッケージ

サンプルプログラムを収録したパッケージを追加する。

# samples
IMAGE_INSTALL_append = " \
             opencv-samples \
             cudnn-samples \
             cuda-samples \
             libvisionworks-sfm-samples \
             libvisionworks-tracking \
             tensorrt-samples \
"

イメージのビルド

最小構成のビルド。

$ bitbake core-image-minimal

この他にcore-image-satocore-image-westonがビルドできることを確認した。

SDカードの作成

ddコマンドでsdcardイメージを書き込む。

$ sudo dd if=./tmp/deploy/core-image-weston-jetson-nano.sdcard of=/dev/sdX bs=100M

/dev/sdXは環境に応じてsdbやsdcなどに適宜変更する。

SDKの作成

次のコマンドでSDKを作成する。今回はcore-image-westonをベースにSDKを作成した。

$ bitbake core-image-weston -c populate_sdk

下記のようなWARNINGが出るが、SDK自体は作成された。

The following packages have unmet dependencies:
 apt-dev : Depends: apt (= 1.2.24-r0) but it is not going to be installed
           Recommends: bash-dev
           Recommends: libcurl-dev but it is not installable
           Recommends: liblzma-dev but it is not installable
           Recommends: shadow-sysroot-dev but it is not installable
 cogl-1.0-dev : Depends: cogl-1.0 (= 1.22.2-r0)
                Recommends: libcogl-dev but it is not installable
                Recommends: libcogl-gles2-dev but it is not installable
                Recommends: libcogl-pango-dev but it is not installable
                Recommends: libcogl-path-dev but it is not installable
 dpkg-dev : Depends: dpkg (= 1.19.4-r0) but it is not going to be installed
            Recommends: dpkg-start-stop-dev but it is not installable
            Recommends: libbz2-dev but it is not installable
            Recommends: liblzma-dev but it is not installable
            Recommends: perl-dev
            Recommends: perl-module-carp-dev but it is not installable
            Recommends: perl-module-constant-dev but it is not installable
            Recommends: perl-module-cwd-dev but it is not installable
            Recommends: perl-module-digest-dev but it is not installable
            Recommends: perl-module-digest-md5-dev but it is not installable
            Recommends: perl-module-errno-dev but it is not installable
            Recommends: perl-module-exporter-dev but it is not installable
            Recommends: perl-module-fcntl-dev but it is not installable
            Recommends: perl-module-feature-dev but it is not installable
            Recommends: perl-module-file-basename-dev but it is not installable
            Recommends: perl-module-file-compare-dev but it is not installable
            Recommends: perl-module-file-copy-dev but it is not installable
            Recommends: perl-module-file-find-dev but it is not installable
            Recommends: perl-module-file-path-dev but it is not installable
            Recommends: perl-module-file-spec-dev but it is not installable
            Recommends: perl-module-file-temp-dev but it is not installable
            Recommends: perl-module-filehandle-dev but it is not installable
            Recommends: perl-module-io-dev but it is not installable
            Recommends: perl-module-io-handle-dev but it is not installable
            Recommends: perl-module-io-seekable-dev but it is not installable
            Recommends: perl-module-list-util-dev but it is not installable
            Recommends: perl-module-overload-dev but it is not installable
            Recommends: perl-module-parent-dev but it is not installable
            Recommends: perl-module-posix-dev but it is not installable
            Recommends: perl-module-scalar-util-dev but it is not installable
            Recommends: perl-module-selectsaver-dev but it is not installable
            Recommends: perl-module-storable-dev but it is not installable
            Recommends: perl-module-symbol-dev but it is not installable
            Recommends: perl-module-term-ansicolor-dev but it is not installable
            Recommends: perl-module-tie-handle-dev but it is not installable
            Recommends: perl-module-tie-hash-dev but it is not installable
            Recommends: perl-module-time-hires-dev but it is not installable
            Recommends: perl-module-time-piece-dev but it is not installable
            Recommends: perl-module-xsloader-dev but it is not installable
            Recommends: update-alternatives-opkg-dev but it is not installable
 target-sdk-provides-dummy-dev : Depends: target-sdk-provides-dummy (= 1.0-r0) but it is not going to be installed
E: Unable to correct problems, you have held broken packages.

まとめ

gccバージョンやコンパイルオプションなど意外と罠が多かった。 結局、jetbackのインストールなどが必要となるので、わざわざyoctoでやる必要は薄い気がした。

ツールチェインを含め、より自由度の高いOSを作成できるのはやはり利点か。

TinyGoをSTM32F4Dicoveryで動かす

はじめに

TinyGoの勉強を兼ねてSTM32F4Dicoveryへ移植してみる。

LチカとHelloWorldを動かせれば良いので、次のペリフェラルのみ動かす。

  • GPIO
  • UART(USART2)

実際に動かしたサンプル。

  • examples/brinky1(Lチカ)
  • examples/brinky2(gofuncの並列Lチカ)
  • examples/serial(HelloWorld)
  • examples/echo(UARTでのエコー)
  • examples/test(golangの基本機能のテスト)

クロック設定は、168MHzに固定

移植作業

追加したファイル

移植に当たって次のファイルを追加した。

ファイル名 ディレクト 概要
stm32f4disco.json targets ターゲット定義
stm32f407.ld targets リンカスクリプト
runtime_stm32f407.go src/runtime クロック初期化など
machine_stm32f407.go src/machine マシン(SoC毎)定義
board_stm32f4disco.go src/machine ボード(ペリフェラル)定義

たった5つのファイルで新しいボードに対応できた。

詳しく見れているわけではないが、抽象化層がよくできている印象。

修正したファイル

既存のファイルの変更はsrc/machine/machine_stm32.goのみ。 GPIOのポートの定義を追加した。

stm32f4disco.json

ターゲットをビルドするための定義ファイル。 ツールチェインやビルド設定、ターゲットへの書き込み方法、デバッガの設定などを定義する。

'inherit'でベースになる定義ファイルを取り込むことができる。

{
  "inherits": ["cortex-m"],
  "llvm-target": "armv7em-none-eabi",
  "build-tags": ["stm32f4disco", "stm32f407", "stm32"],
  "cflags": [
    "--target=armv7em-none-eabi",
    "-Qunused-arguments"
  ],
  "ldflags": [
    "-T", "targets/stm32f407.ld"
  ],
  "extra-files": [
    "src/device/stm32/stm32f407.s"
  ],
  "flash": "openocd -f interface/stlink-v2.cfg -f target/stm32f4x.cfg -c 'program {hex} reset exit'",
  "ocd-daemon": ["openocd", "-f", "interface/stlink.cfg", "-f", "target/stm32f4x.cfg"],
  "gdb-initial-cmds": ["target remote :3333", "monitor halt", "load", "monitor reset", "c"]
}

'build-tags'で、'ボード名'、'マシン名'、'アーキテクチャ'を設定する。 Linuxカーネルを覗いた事がある人なら、'board'、'mach'、'arch'のようにイメージすれば良さそう。 ここに指定した文字列を元に、ビルドするターゲットのソースファイルを選択しているっぽい。

使用するリンカスクリプトもここで定義する。

stm32f407.ld

RAMは192K使えるはずなんだけど、とりあえず128Kにしといた。

MEMORY
{
    FLASH_TEXT (rw) : ORIGIN = 0x08000000, LENGTH = 1M
    RAM (xrw)       : ORIGIN = 0x20000000, LENGTH = 128K
}

_stack_size = 4K;

INCLUDE "targets/arm.ld"

runtime_stm32f407.go

クロック関連の初期化や処理を書く。 スリープやgofuncのスケジューラから呼ばれて、タイマーなどで実際のタイミングを取るための処理。

ここで実装した関数は次の通り。

  • init
  • putchar
  • initCLK
  • initTIM3
  • initTIM7
  • sleepTicks
  • ticks
  • timerSleep
  • handleTIM3
  • handleTIM7

ビルド対象選択のギミック

ソースファイルの先頭に次のようなコメントがある。

// +build stm32,stm32f407

これがビルドするターゲットが「stm32,stm32f407」だった場合にこのファイルをビルドするという宣言になっているらしい。

クロック初期化

initCLKはクロック初期化の処理。platformioや他のベアメタル系の処理を参考(ほぼパクリ)に実装した。

ここで重要なことはPLLの設定とそれぞれのプリスケーラの設定の計算。

システムクロックが168MHzになるように設定している。 外部クロックである`HSE'の値は8MHzなので、これを基準にクロックを計算する。

const (
    HSE_STARTUP_TIMEOUT = 0x0500
    /* PLL Options - See RM0090 Reference Manual pg. 95 */
    PLL_M = 8    /* PLL_VCO = (HSE_VALUE or HSI_VLAUE / PLL_M) * PLL_N */
    PLL_N = 336
    PLL_P = 2    /* SYSCLK = PLL_VCO / PLL_P */
    PLL_Q = 7    /* USB OTS FS, SDIO and RNG Clock = PLL_VCO / PLL_Q */
)

また、それぞれのクロックドメインを次のように設定した。

クロック 周波数
SYSCLK 168mhz
HCLK 168mhz
APB2(PCLK2) 84mhz
APB1(PCLK1) 42mhz

Sleep処理

Lチカなどで一定時間待ち合わせたりする際に必要なsleepの処理のためにTIM3を使用する

TIM3はAPB1に接続されているので、内部クロックソースの場合は42mhzになるかと思いきや、 タイマーは自分でPLLを持っているらしく何も設定しない場合は42mhzの倍の84mhzで動作するらしい。

このタイマーは、プリスケーラで分周したクロックで、ARRに設定したカウント分に到達した時に割り込みを発生させることができる。

tinygoでのsleepはマイクロ秒単位で指定なので、たとえば500ミリ秒スリープしたい場合は500000000が渡されてくる。

参考にした実装では、タイマーのクロックを10Khzに分周して、ARRを0.1ミリ秒単位で設定することで、 スリープ解除したいタイミングで割り込みが発生するようにしていた。

なのでこのようなコードになる。

// ticks are in microseconds
func timerSleep(ticks uint32) {
    timerWakeup = false

    // CK_INT = APB1 x2 = 84mhz
    // prescale counter down from 84mhz to 10khz aka 0.1 ms frequency.
    stm32.TIM3.PSC = 84000000/10000 - 1 // 8399

    // set duty aka duration
    arr := (ticks / 100) - 1 // convert from microseconds to 0.1 ms
    if arr == 0 {
        arr = 1 // avoid blocking
    }
    stm32.TIM3.ARR = stm32.RegValue(arr)

    // Enable the hardware interrupt.
    stm32.TIM3.DIER |= stm32.TIM_DIER_UIE

    // Enable the timer.
    stm32.TIM3.CR1 |= stm32.TIM_CR1_CEN

    // wait till timer wakes up
    for !timerWakeup {
        arm.Asm("wfi")
    }
}

84Mhzから10Khzを作るためにプリスケーラレジスタのPSCに8399を設定。 10Khzは1秒に10000回振幅するので、1回の振幅に0.1ミリ秒かかる計算になる。

引数で渡されたtickはマイクロ秒単位なので、0.1ミリ秒単位になるようにARRを設定する。 割り込みハンドラの中で、スリープ解除フラグを立てるという前提で、フラグが経っていない時はwfiで寝る。

なかなかに分かりやすい構造となっている。

割り込みを有効化するためには、NVICに対する設定を行なう次の関数を使用する。

   arm.SetPriority(stm32.IRQ_TIM3, 0xc3)
    arm.EnableIRQ(stm32.IRQ_TIM3)

そしてハンドラは次のようにする。

//go:export TIM3_IRQHandler
func handleTIM3() {
    if (stm32.TIM3.SR & stm32.TIM_SR_UIF) > 0 {
        // Disable the timer.
        stm32.TIM3.CR1 &^= stm32.TIM_CR1_CEN

        // clear the update flag
        stm32.TIM3.SR &^= stm32.TIM_SR_UIF

        // timer was triggered
        timerWakeup = true
    }
}

//go:export TIM3_IRQHandlerのコメント行がキモとなっていて、この次に定義された関数がTIM3_IRQHandlerとしてエクスポートされる。 つまり、割り込みハンドラとして登録されるようになっている。

gofuncスケジューラのためのtickカウンタ

examples/blinky2では2つのLEDをgofuncを使用して2つの関数からそれぞれ制御する。 tinygoは内部にスケジューラを持っていて、スケジューラがタイミングを取るためにticks関数を呼び出している。

ticks関数は、ボードが起動してからのカウントをマイクロ秒単位で取得する。

// number of ticks (microseconds) since start.
func ticks() timeUnit {
    // milliseconds to microseconds
    return tickCount * 1000
}

ここで返すカウント値は、TIM7を使って1ミリ秒ごとにカウントアップすることで作る。(なので戻り値は1000倍している)

TIM7の設定は基本的にTIM3と同じ。違うのはARRが10固定になっている点。

PSCで10Khzを作って、0.1ミリ秒の振幅が10回行われた時、つまり1ミリ秒に1回割り込みが発生する。 その中でカウンタを増やす。

// Enable the TIM7 clock.(tick count)
func initTIM7() {
    stm32.RCC.APB1ENR |= stm32.RCC_APB1ENR_TIM7EN

    // CK_INT = APB1 x2 = 84mhz
    stm32.TIM7.PSC = 84000000/10000 - 1     // 84mhz to 10khz(0.1ms)
    stm32.TIM7.ARR = stm32.RegValue(10) - 1 // interrupt per 1ms

    // Enable the hardware interrupt.
    stm32.TIM7.DIER |= stm32.TIM_DIER_UIE

    // Enable the timer.
    stm32.TIM7.CR1 |= stm32.TIM_CR1_CEN

    arm.SetPriority(stm32.IRQ_TIM7, 0xc1)
    arm.EnableIRQ(stm32.IRQ_TIM7)
}
...(snip)...

//go:export TIM7_IRQHandler
func handleTIM7() {
    if (stm32.TIM7.SR & stm32.TIM_SR_UIF) > 0 {
        // clear the update flag
        stm32.TIM7.SR &^= stm32.TIM_SR_UIF
        tickCount++
    }
}

machine_stm32f407.go

マシン(SoC)固有の処理を書く。 ペリフェラルのドライバはここに書くことになる。

今回は次のドライバを実装している。

  • GPIO
  • UART

GPIOドライバ

GPIOドライバでは次の関数を実装している。

  • getPort
  • enableClock
  • Configure
  • setAltFunc
  • Set

この内外部から呼び出される可能性があるのは大文字で始まっているConfigureSetのみ。 golangでは大文字で始まるシンボルがエクスポートされるらしい。

getPort

この関数ではGPIOA〜GPIOIまでのレジスタアクセス用の構造体を取得できる。

enableClock

この関数はAHB1のGPIOxENを有効にしデバイスを有効化する。

Configure

この関数は外部から呼び出され、ピンの方向やプッシュプルなどの設定を行なう。

指定できるフラグは次のようになっている。

フラグ 機能 備考
GPIO_OUTPUT 出力
GPIO_INPUT 入力、GPIO_INPUT_PULLDOWNと同じ
GPIO_INPUT_FLOATING 入力でフローティング
GPIO_INPUT_PULLDOWN 入力でプルダウン
GPIO_INPUT_PULLUP 入力でプルアップ
GPIO_UART_TX UARTのTX設定 UARTドライバからのみ呼ばれる
GPIO_UART_RX UARTのRX設定 UARTドライバからのみ呼ばれる

setAltFunc

GPIOのAlternative Functionを設定する。

Set

出力ピンのHI/LOを設定する。

UARTドライバ

UARTドライバはほぼmachine_stm32f103xx.go(BluePill向け)のドライバを拝借した。

ボーレートの設定の部分だけSTM32F4Discoveryに合わせて設定した。 本来であれば9600や38400など一般的な設定は受け付けるようにするべきだが、 今回は115200に固定とした。

ボーレートを決定するにはBRRレジスタを設定する必要がある。

BRRに設定できる値はRM0090のTable 134〜143に一覧されている。

USART2はAPB2に接続されているので、クロックは42Mhzとなる。USART2_CR1のOVER8は0に設定されているのでTable 142の値を使用できる。

f:id:mickey_happygolucky:20190416115524p:plain
Table 142(抜粋)

BRRは指数部が4ビットの固定小数点のため、下記のように計算できる。

(整数部<<4)+(指数部*16)

なのでこのように実装している。

// Configure the UART.
func (uart UART) Configure(config UARTConfig) {
    // Default baud rate to 115200.
    if config.BaudRate == 0 {
        config.BaudRate = 115200
    }

    // pins
    switch config.TX {
    default:
        // use standard TX/RX pins PA2 and PA3
        GPIO{UART_TX_PIN}.Configure(GPIOConfig{Mode: GPIO_UART_TX})
        GPIO{UART_RX_PIN}.Configure(GPIOConfig{Mode: GPIO_UART_RX})
    }

    // Enable USART2 clock
    stm32.RCC.APB1ENR |= stm32.RCC_APB1ENR_USART2EN

    /*
     Set baud rate(115200)
     OVER8 = 0, APB2 = 42mhz
     +----------+--------+
     | baudrate | BRR    |
     +----------+--------+
     | 1200     | 0x88B8 |
     | 2400     | 0x445C |
     | 9600     | 0x1117 |
     | 19200    | 0x88C  |
     | 38400    | 0x446  |
     | 57600    | 0x2D9  |
     | 115200   | 0x16D  |
     +----------+--------+
   */
    stm32.USART2.BRR = 0x16c

    // Enable USART2 port.
    stm32.USART2.CR1 = stm32.USART_CR1_TE | stm32.USART_CR1_RE | stm32.USART_CR1_RXNEIE | stm32.USART_CR1_UE

    // Enable RX IRQ.
    arm.SetPriority(stm32.IRQ_USART2, 0xc0)
    arm.EnableIRQ(stm32.IRQ_USART2)
}

デバッグ中に0x16cをBRRに設定したままコミットしてしまった。 0x16dを入れても問題なく動く。

board_stm32f4disco.go

このファイルではボードに実装されているピンやペリフェラルを定義する。

// +build stm32,stm32f4disco

package machine

const (
    PA0  = portA + 0
    PA1  = portA + 1
    PA2  = portA + 2
    PA3  = portA + 3
    PA4  = portA + 4
    PA5  = portA + 5
    PA6  = portA + 6
    PA7  = portA + 7
    PA8  = portA + 8
    PA9  = portA + 9
    PA10 = portA + 10
    PA11 = portA + 11
    PA12 = portA + 12
    PA13 = portA + 13
    PA14 = portA + 14
    PA15 = portA + 15

    PB0  = portB + 0
    PB1  = portB + 1
    PB2  = portB + 2
    PB3  = portB + 3
    PB4  = portB + 4
    PB5  = portB + 5
    PB6  = portB + 6
    PB7  = portB + 7
    PB8  = portB + 8
    PB9  = portB + 9
    PB10 = portB + 10
    PB11 = portB + 11
    PB12 = portB + 12
    PB13 = portB + 13
    PB14 = portB + 14
    PB15 = portB + 15

    PC0  = portC + 0
    PC1  = portC + 1
    PC2  = portC + 2
    PC3  = portC + 3
    PC4  = portC + 4
    PC5  = portC + 5
    PC6  = portC + 6
    PC7  = portC + 7
    PC8  = portC + 8
    PC9  = portC + 9
    PC10 = portC + 10
    PC11 = portC + 11
    PC12 = portC + 12
    PC13 = portC + 13
    PC14 = portC + 14
    PC15 = portC + 15

    PD0  = portD + 0
    PD1  = portD + 1
    PD2  = portD + 2
    PD3  = portD + 3
    PD4  = portD + 4
    PD5  = portD + 5
    PD6  = portD + 6
    PD7  = portD + 7
    PD8  = portD + 8
    PD9  = portD + 9
    PD10 = portD + 10
    PD11 = portD + 11
    PD12 = portD + 12
    PD13 = portD + 13
    PD14 = portD + 14
    PD15 = portD + 15

    PE0  = portE + 0
    PE1  = portE + 1
    PE2  = portE + 2
    PE3  = portE + 3
    PE4  = portE + 4
    PE5  = portE + 5
    PE6  = portE + 6
    PE7  = portE + 7
    PE8  = portE + 8
    PE9  = portE + 9
    PE10 = portE + 10
    PE11 = portE + 11
    PE12 = portE + 12
    PE13 = portE + 13
    PE14 = portE + 14
    PE15 = portE + 15

    PH0 = portH + 0
    PH1 = portH + 1
)

const (
    LED         = LED_BUILTIN
    LED1        = LED_GREEN
    LED2        = LED_ORANGE
    LED3        = LED_RED
    LED4        = LED_BLUE
    LED_BUILTIN = LED_GREEN
    LED_GREEN   = PD12
    LED_ORANGE  = PD13
    LED_RED     = PD14
    LED_BLUE    = PD15
)

// UART pins
const (
    UART_TX_PIN = PA2
    UART_RX_PIN = PA3
)

特に難しいところはない。

tinygoコマンド

ソースコードをビルドしたり、プログラムを実機に書き込んだりするにはtinygoコマンドを使用する。

単にビルドするのであればbuildサブコマンドを使用する。

$ tinygo build -o test.elf --target stm32f4disco examples/echo

プログラムを書き込む場合はflashサブコマンドを使用する。flashではビルドから書き込みまでやってくれるので便利。

$ tinygo flash --target stm32f4disco examples/echo

GDBデバッグする場合はgdbサブコマンドを使用する。これも必要に応じてビルド、書き込みも行ってくれる。

$ tinygo gdb --target stm32f4disco examples/echo

シンボルデバッグは正直使えないため、レジスタの確認が主な用途だった。

Pull Request

せっかく作ったのでプルリクを送ってみた。

CONTRIBUTING.mdを読んでなかった ため、masterブランチに向けてプルリクを作成したり(devブランチに向けないとダメ)、 go fmtを実行していなかったりといろいろ合ったが、中の人が丁寧にアドバイスをくれた。

最終的には中の人がいろいろと手直しをしてくれてdevブランチにマージされた。

とてもいい経験になった。

まとめ

Golang初心者でも、なんとかSTM32F4DiscoveryでTinyGo動かすことができた。 ビルドシステムやコンパイラがなかなか良くできている印象。

ハードウェアの抽象化レベルが高いのか、少しのコードを足すだけで新しいボードに対応できるのはスゴいと思った。

あと、中の人がとても親切。

ただ、GDBが使えるとはいえ、シンボルでバッグがまともに使えないので(goのシンボルとは1:1にならないので?)、 UARTが使えるようになるまではデバッグがしんどかった。

環境が整ってくればかなり使えるのではないかと思う。 意外とGolangのコードを修正して実機で確認するというサイクルが手軽に回せるので開発しやすいと思った。

tinygoでBluePill

はじめに

tinygoを作ったので、BluePillで動かしてみる。

ここの手順を参考にする。

テストを実行

このコマンドはどこのディレクトリでも実行可能。

$ tinygo run examples/test

バイスターゲットの生成

これは最初にやっておく必要があるらしい。

$ cd $GOPATH/src/github.com/tinygo-org/tinygo
$ make gen-device

BluePillとST-Link V2の接続

次のように接続する。

ST-Link V2 Pin ターゲット
GND 20 GND
TCK 9 CLK
TMS 7 IO
TVCC 1 3.3V

ST-Link V2とPCをUSBケーブルで接続する。

次にBluePillのマイクロUSBのポートとPCをUSBで接続し、ボードに電源を供給する。

サンプルプログラムを書き込む

次のコマンドでBluePillにLチカプログラムを書き込む。

$ tinygo flash -target=bluepill examples/blinky1
Open On-Chip Debugger 0.10.0+dev-00664-g8417a569 (2019-01-27-16:38)
Licensed under GNU GPL v2
For bug reports, read
    http://openocd.org/doc/doxygen/bugs.html
WARNING: interface/stlink-v2.cfg is deprecated, please switch to interface/stlink.cfg
Info : auto-selecting first available session transport "hla_swd". To override use 'transport select <transport>'.
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
adapter speed: 1000 kHz
adapter_nsrst_delay: 100
none separate
Info : clock speed 1000 kHz
Info : STLINK V2J14S3 (API v2) VID:PID 0483:3748
Info : Target voltage: 3.217323
Info : stm32f1x.cpu: hardware has 6 breakpoints, 4 watchpoints
Info : Listening on port 3333 for gdb connections
target halted due to debug-request, current mode: Thread 
xPSR: 0x01000000 pc: 0x08000130 msp: 0x20000d50
** Programming Started **
auto erase enabled
Info : device id = 0x20036410
Info : flash size = 64kbytes
target halted due to breakpoint, current mode: Thread 
xPSR: 0x61000000 pc: 0x2000003a msp: 0x20000d50
wrote 3072 bytes from file /tmp/tinygo866341559/main.hex in 0.231905s (12.936 KiB/s)
** Programming Finished **
** Resetting Target **
shutdown command invoked

LEDがチカチカしたら成功。

Blinky1のソース

ちなみに、書き込んだBlinky1のソースは$GOPATH/src/github.com/tinygo-org/tinygo/src/examples/blinky1にある。

package main

// This is the most minimal blinky example and should run almost everywhere.

import (
    "machine"
    "time"
)

func main() {
    led := machine.GPIO{machine.LED}
    led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
    for {
        led.Low()
        time.Sleep(time.Millisecond * 500)

        led.High()
        time.Sleep(time.Millisecond * 500)
    }
}

まとめ

tinygoが正しく作られてさえいれば難しいところはない。

Ubuntu 18.04でllvm-7系パッケージを削除する方法

はじめに

地味にデスクトップ環境が依存しているので引っこ抜くのが大変

削除方法

gvfs-daemonsがlibllvm7に依存しているらしいのでちょっと大変

まずは消せるだけ消す。

$ sudo apt purge -y clang-7 lldb-7 lld-7
$ sudo apt purge -y libllvm-7-ocaml-dev llvm-7-dev llvm-7-doc llvm-7-examples llvm-7-runtime
$ sudo apt purge llvm-7 
$ sudo apt purge -y clang-7 clang-tools-7 clang-7-doc libclang-common-7-dev libclang-7-dev libclang1-7 clang-format-7 python-clang-7
$ sudo apt purge -y lldb-7 lld-7 libc++-7-dev libc++abi-7-dev 
$ sudo apt purge -y llvm-7-dev

gvfsを消す。

$ sudo apt purge -y gvfs
$ sudo apt purge -y libllvm7 

これでできた。

デスクトップのインストール

このままでは、デスクトップ環境に必要なパッケージがautoremoveの対象となってしまう。

以下のパッケージが自動でインストールされましたが、もう必要とされていません:
  apg apturl-common bolt camlp4 cheese-common gir1.2-accountsservice-1.0
  gir1.2-gck-1 gir1.2-gcr-3 gir1.2-gdesktopenums-3.0 gir1.2-gdm-1.0
  gir1.2-gnomebluetooth-1.0 gir1.2-gnomedesktop-3.0 gir1.2-gweather-3.0
  gir1.2-nm-1.0 gir1.2-nma-1.0 gir1.2-totemplparser-1.0 gir1.2-upowerglib-1.0
  gjs gkbd-capplet gnome-control-center-faces gnome-session-common
  gnome-shell-common gnome-themes-extra gnome-themes-extra-data
  gnome-todo-common gnome-video-effects grilo-plugins-0.3-base
  gtk2-engines-pixbuf gvfs-libs ledit libbrotli1 libcamlp4-ocaml-dev
  libcdio-cdda2 libcdio-paranoia2 libclutter-1.0-common libcogl-common
  libcolord-gtk1 libctypes-ocaml libctypes-ocaml-dev libdazzle-1.0-0
  libdrm-amdgpu1 libdrm-nouveau2 libdrm-radeon1 libegl-mesa0 libegl1
  libegl1-mesa libexiv2-14 libfindlib-ocaml libfindlib-ocaml-dev libfontenc1
  libgail-3-0 libgbm1 libgdm1 libgexiv2-2 libgjs0g libglapi-mesa libgles2
  libglvnd0 libgnome-autoar-0-0 libgnome-todo libgnomekbd-common libgnomekbd8
  libgom-1.0-0 libgraphene-1.0-0 libgrilo-0.3-0 libinput-bin libinput10
  liblirc-client0 liblua5.3-0 libmediaart-2.0-0 libmozjs-52-0 libmtdev1
  libmtp-common libmtp-runtime libmtp9 libnss-myhostname libsysmetrics1
  libtracker-sparql-2.0-0 libwayland-egl1-mesa libwayland-server0 libwoff1
  libxcb-dri2-0 libxcb-dri3-0 libxcb-glx0 libxcb-icccm4 libxcb-image0
  libxcb-keysyms1 libxcb-present0 libxcb-randr0 libxcb-render-util0
  libxcb-res0 libxcb-shape0 libxcb-sync1 libxcb-xfixes0 libxcb-xkb1 libxcb-xv0
  libxfont2 libxkbcommon-x11-0 libxklavier16 libxshmfence1 libxvmc1
  libxxf86dga1 mutter-common ocaml-base-nox ocaml-compiler-libs ocaml-findlib
  ocaml-interp ocaml-nox python-lldb-7 python3-macaroonbakery python3-nacl
  python3-protobuf python3-pymacaroons python3-rfc3339 python3-tz
  shotwell-common totem-common ubuntu-system-service x11-apps
  x11-session-utils x11-xkb-utils xfonts-base xfonts-encodings xfonts-scalable
  xfonts-utils xinit xinput xserver-common xserver-xorg-legacy yelp-xsl
  zenity-common
これを削除するには 'sudo apt autoremove' を利用してください。
以下のパッケージは「削除」されます:
  apturl* cheese* deja-dup* gdm3* gir1.2-mutter-2* gir1.2-totem-1.0*
  gir1.2-webkit2-4.0* gnome-calendar* gnome-control-center*
  gnome-getting-started-docs* gnome-initial-setup* gnome-online-accounts*
  gnome-session-bin* gnome-shell* gnome-startup-applications* gnome-todo*
  gnome-user-docs* gnome-user-guide* gstreamer1.0-clutter-3.0*
  gstreamer1.0-gl* gvfs-daemons* libcheese-gtk25* libcheese8*
  libclutter-1.0-0* libclutter-gst-3.0-0* libclutter-gtk-1.0-0*
  libcogl-pango20* libcogl-path20* libcogl20* libedataserverui-1.2-2* libgl1*
  libgl1-mesa-dri* libgl1-mesa-glx* libglu1-mesa* libglx-mesa0* libglx0*
  libgoa-backend-1.0-1* libgstreamer-gl1.0-0* liblldb-7* libllvm7*
  libmutter-2-0* libtotem0* libwebkit2gtk-4.0-37* libxatracker2* libyelp0*
  mutter* shotwell* totem* totem-plugins* ubuntu-docs*
  ubuntu-release-upgrader-gtk* ubuntu-session* update-manager*
  update-notifier* x11-utils* xorg* xserver-xephyr* xserver-xorg*
  xserver-xorg-core* xserver-xorg-input-all* xserver-xorg-input-libinput*
  xserver-xorg-input-wacom* xserver-xorg-video-all* xserver-xorg-video-amdgpu*
  xserver-xorg-video-ati* xserver-xorg-video-fbdev* xserver-xorg-video-intel*
  xserver-xorg-video-nouveau* xserver-xorg-video-qxl*
  xserver-xorg-video-radeon* xserver-xorg-video-vesa*
  xserver-xorg-video-vmware* xwayland* yelp* zenity*
アップグレード: 0 個、新規インストール: 0 個、削除: 75 個、保留: 0 個。

これでは困るので、次のようにする。

$ sudo apt install -y gvfs ubuntu-desktop

これで一応次回もデスクトップ環境で起動してくれる。

まとめ

最後にubuntu-desktopをインストールすれば万事解決(ぉ