みつきんのメモ

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

Giant BoardをDebian GNU/Linux 9 (stretch)で動かす

Giant Boardが届いた。

このボードにはCortext-A5コアのATSAMA5D27-D1G-CUが搭載されている。

MICROCHIPによると、こういうものらしい。

The SAMA5 series are high-performance, ultra-low power ARM Cortex-A5 core based MPU devices. They support multiple memories, including DDR3, LPDDR3, and QSPI Flash.

ATSAMA5D27MPU System in Packages (SiPs)というもので、パッケージの中にDDRも入っている。 なので、他にメモリを配置する必要がないためボードがかなり小さい。

Cortex-AなのでLinuxも動く。というか標準サポートのOSがDebianとなっている。

How to build custom Giant Board device images.を参考にOSをビルドしてみた。

作業環境はUbuntu 18.04

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

device tree compilerをインストールする。18.04のaptでインストールできるバージョンは古いので下記から取ってくる。

$ cd /tmp
$ wget http://mirrors.edge.kernel.org/ubuntu/pool/main/d/device-tree-compiler/libfdt1_1.5.1-1_amd64.deb
$ wget http://mirrors.edge.kernel.org/ubuntu/pool/main/d/device-tree-compiler/device-tree-compiler_1.5.1-1_amd64.deb
$ sudo gdebi libfdt1_1.5.1-1_amd64.deb
$ sudo gdebi device-tree-compiler_1.5.1-1_amd64.deb

ツールのセットアップ

ビルド用のスクリプトを使用する。

$ git clone https://github.com/Groboards/giantboard-tools.git
$ cd giantboard-tools
$ chmod +x build_menu.sh
$ ./build_menu.sh

メニューを起動する。

Build Options:
1: Setup Build Environment.(Run on first setup.)
2: Build at91bootstrap
3: Build u-boot
4: Build kernel/clean
5: Rebuild kernel
6: Build debian rootfs
7: Chroot into rootfs
8: Build device overlays
9: Make bootable device image
Enter selection [1-7] > 

初回実行時は1: Setup Build Environment.(Run on first setup.)を選択する。

それ以降は必要に応じて実行することになるが、まっさらな状態では次の順序で実行することになる。

| 2: Build at91bootstrap | 3: Build u-boot | 4: Build kernel/clean | 6: Build debian rootfs | 8: Build device overlays | 9: Make bootable device image

Build device overlaysで下記の様なエラーが出力されるが、特に問題はなさそう?

<stdin>:20.22-29.6: Warning (spi_bus_reg): /fragment@1/__overlay__/ethernet@1: SPI bus unit address format error, expected "0"
done building..

Make bootable device imagegiantboard.imgが生成される。

これをddコマンドなどでSDに書き込んで起動する。

$ sudo dd if=giantboard.img of=/dev/sdX bs=100M

/dev/sdXは環境によって適宜読み替える必要がある。

起動

ピンヘッダに出ているTX,RX,GNDをUSB-シリアル変換ケーブルでPCに接続し、minicomでログインする。

アカウントは次の通り。

item value
user root
passwd root

次のようになればOK。

Debian GNU/Linux 9 giantboard ttyS0

Login incorrect
giantboard login: root
Password:
Linux giantboard 5.0.0+ #1 Mon Feb 17 19:03:20 JST 2020 armv7l
   _____ _             _     ____                      _
  / ____(_)           | |   |  _ \                    | |
 | |  __ _  __ _ _ __ | |_  | |_) | ___   __ _ _ __ __| |
 | | |_ | |/ _` | '_ \| __| |  _ < / _ \ / _` | '__/ _` |
 | |__| | | (_| | | | | |_  | |_) | (_) | (_| | | | (_| |
  \_____|_|\__,_|_| |_|\__| |____/ \___/ \__,_|_|  \__,_|


root@giantboard:~# uname -a
Linux giantboard 5.0.0+ #1 Mon Feb 17 19:03:20 JST 2020 armv7l GNU/Linux
root@giantboard:~#

まとめ

giantboardが動いた。カーネルのージョンはもともと4.14とされていたのだが、意外にも5.0.0だった。

電源については少し不安定な様で、ボードのUSBポートへVBUSで給電するよりも、USB-シリアル変換の5VをボードのVBATに入れるのが安定しているように見えた。

素人ながら回路図を確認した感じだと、VBATは入ってすぐにMIC5247-2.0に入るので、 VBATは5V入れても問題なさそう。(わからんけど)

今のところは元気に動いているが、ちょっと電力を使うようなものを動かすとリブート祭りになる。

Bashスクリプトで複数のテーブルを組み合わせて使用する

はじめに

タイトルいったい何のことか伝わらないと思うので。

いわゆる2次元配列っぽいことをしたいというか、要素数の異なる複数のテーブルを多重ループで回す感じ。

2次元配列が近いと思って調べたけど、自分のやりたいこととうまく合致しないかな。と。

やりたいこと

例えば、複数の環境と対象としてテストプログラムを実行したいが、環境毎に実行するプログラムが異なる。

環境と環境毎のテストプログラムのテーブルをそれぞれもってて、それを組み合わせてループさせたい。

(C++らしなく)C++で書くとこんな感じのやつ。

#include <cstdio>
#include <vector>

int main() {
    enum {
        Env1,
        Env2,
        Env3,
        Env_Max,
    };
    std::vector<const char*> env1_test {"testA", "testB", "testC"};
    std::vector<const char*> env2_test {"testD"};
    std::vector<const char*> env3_test {"testA", "testB", "testC", "testD"};
    std::vector<std::vector<const char*>> table {
            {env1_test}, {env2_test}, {env3_test},
    };
    for (auto i = 0; i < Env_Max; ++i) {
        for (auto elem : table[i]) {
            std::printf("Env[%d]:%s\n", i, elem);
        }
    }
    return 0;
}

実行結果

Env[0]:testA
Env[0]:testB
Env[0]:testC
Env[1]:testD
Env[2]:testA
Env[2]:testB
Env[2]:testC
Env[2]:testD

環境毎のテストプログラムの数が異なるのがポイント。

やったこと

bashスクリプトでは多重配列が無い(はず?)なので、このようなケースは意外と難しい。

ほとんど型の概念がなく、文字列処理としての側面が強いので、実行時に変数名を合成して複数の配列を組み合わせることにした。

#!/bin/bash

readonly -a Env1_test=(
    testA
    testB
    testC
)

readonly -a Env2_test=(
    testD
)

readonly -a Env3_test=(
    testA
    testB
    testC
    testD
)

readonly -a Environments=(
    Env1
    Env2
    Env3
)

execute() {
    local env=$1
    local test=$2
    echo ${env} ${test}
}

execute_all() {
    for env in ${Environments[@]} ; do
        local -n tests=${env}_test
        for t in ${tests[@]} ; do
            execute ${env} ${t}
        done
    done
}

execute_all

実行結果

$ ./test.sh
Env1 testA
Env1 testB
Env1 testC
Env2 testD
Env3 testA
Env3 testB
Env3 testC
Env3 testD

execute_allの「local -n tests=${env}_test」の行で変数名を合成している。 ここではEnvironments配列の内容であるEnvXと固定の文字列_testを合成してEnvX_testという変数名を作成して、 その中の要素にループでアクセスしている。-nがポイントで、変数を名前参照とすることで、 展開後の変数と文字列を組み合わせて、別に実体のある変数の参照をローカル変数のtestsに格納している。

これをしないと、実体が空のEnvX_testがtestsに格納されるので意図した動きにならない。

まとめ

名前参照を使うと意外と便利なことができる。 ただしやりすぎると破綻する。

PlatformIO Digisparkを動かす

はじめに

ShigezoneでDigisparkを購入したのPlatformIOで動かしてみる。

書き込みの時にコツがいるのでメモっておく。

プロジェクトの作成

作業用ディレクトリを次のように作成する。

$ mkdir -p ~/pio/digispark
$ cd ~/pio/digispark

このボードのCPUはATTINY85なのでボードをdigispark-tinyに設定する。

$ pio init -b digispark-tiny

プログラムの作成

platformio.iniはデフォルトのままで良い。

src/main.cppを次の内容で作成する。

#include <Arduino.h>

#define LED (1)

void setup() {
    pinMode(LED, OUTPUT);
}

void loop() {
    digitalWrite(LED, HIGH);
    delay(500);
    digitalWrite(LED, LOW);
    delay(500);
}

ビルドと書き込み

次のコマンドでビルド。

$ pio run

書き込みでエラー

書き込みで使用しているmicronucleusがエラーにになる。

$ pio run -t upload
(...省略 ...)
micronucleus: library/micronucleus_lib.c:66: micronucleus_connect: Assertion `res >= 4' failed.
Aborted (core dumped)
*** [upload] Error 134

有名なエラーのようでトラブルシューティングは見つかる。

ここによると、udevルールを作成すると良いとある。

/etc/udev/rules.d/49-micronucleus.rules

# UDEV Rules for Micronucleus boards including the Digispark.
# This file must be placed at:
#
# /etc/udev/rules.d/49-micronucleus.rules    (preferred location)
#   or
# /lib/udev/rules.d/49-micronucleus.rules    (req'd on some broken systems)
#
# After this file is copied, physically unplug and reconnect the board.
#
SUBSYSTEMS=="usb", ATTRS{idVendor}=="16d0", ATTRS{idProduct}=="0753", MODE:="0666"
KERNEL=="ttyACM*", ATTRS{idVendor}=="16d0", ATTRS{idProduct}=="0753", MODE:="0666", ENV{ID_MM_DEVICE_IGNORE}="1"
#
# If you share your linux system with other users, or just don't like the
# idea of write permission for everybody, you can replace MODE:="0666" with
# OWNER:="yourusername" to create the device owned by you, or with
# GROUP:="somegroupname" and mange access using standard unix groups.

次のコマンドで設定を反映する。

$ sudo udevadm control --reload-rules

それでも発生するエラー

バイスへのアクセス権限の問題だと言われていたが、それを解決したはずなのにまだこのエラーが出る。

micronucleus: library/micronucleus_lib.c:66: micronucleus_connect: Assertion `res >= 4' failed.

試行錯誤を繰り返し辿り着いた解決方法。

ボードとPCをUSBケーブルで接続しているが、書き込む前に一度接続を外すとうまく行くことがわかった。

手順としては次のようになる。

  1. USBケーブルを抜去
  2. 書き込みコマンドを実行
  3. USBケーブルを挿入
  4. 書き込み成功

まず、USBケーブルを抜いて接続されていない状態にする。

$ pio run -t upload
(...省略...)
AVAILABLE: micronucleus
CURRENT: upload_protocol = micronucleus
Uploading .pio/build/digispark-tiny/firmware.hex

この表示が出たところで、ケーブルを接続する。

> Please plug in the device ... 
> Press CTRL+C to terminate the program.
> Device is found!
connecting: 40% complete
> Device has firmware version 1.6
> Available space for user applications: 6012 bytes
> Suggested sleep time between sending pages: 8ms
> Whole page count: 94  page size: 64
> Erase function sleep duration: 752ms
parsing: 60% complete
> Erasing the memory ...
erasing: 80% complete
> Starting to upload ...
writing: 100% complete
>> Micronucleus done. Thank you!
=================================== [SUCCESS] Took 46.83 seconds ===================================

このようにメッセージが表示されれば書き込み成功。

まとめ

micronucleusでの書き込みには若干のコツが必要となり手間取った。 Google先生によって、エラーメッセージとudevルールまでは簡単にたどり着くが、USBでの接続を一度切るというところまではなかなか見つけられなかった。

無事、LEDがチカチカしたのでOK。

ASF傘下に入ったNuttXをビルド

はじめに

NuttXはASFのIncubatorプロジェクトへ採択され、リポジトリがBitBucketからGitHubに変更された。

今後はプルリクエストなどもGitHubで受け付ける。

ソースコードを取得

GitHubからソースコードを取得するには次のようにする。

$ git clone https://github.com/apache/incubator-nuttx.git nuttx
$ git clone https://github.com/apache/incubator-nuttx-apps.git apps

ビルド

今のところ、ビルド手順に変更はない。

STM32F4DiscoveryのUSB NuttX Shell環境向けにビルドする。

$ cd nuttx
$ tools/configure.sh stm32f4discovery:usbnsh
$ make -j $(nproc)

書き込み

試しに書き込んでみる。

$ st-flash write nuttx.bin 0x8000000

まとめ

リポジトリ名がそれぞれincubator-nuttxincubator-nuttx-appsに変更されているので注意が必要。

incubator期間が終わったらまた名前が変わるのだろうか。

PlatformIO mbedのビルド時間を短縮するスクリプトを作った

はじめに

2020/3/6 追記

PlatformIOでframeworkをmbedにすると必要のない機能のソースコードコンパイルするため、 ビルド時間がかなり長くなってしまう。

| Mbed for STM32 compiles toooooo long | mbed + PlatformIO = too long compilation

これは度々話題に挙げられていて.mbedignoreで不要なソースをフィルタできると答えが出るのだが、

Does .mbedignore still work?

.mbedignoreは適切な場所に配置しないと効力を発揮しないことと、 適切な配置場所の具体的な例が乏しいため、実際のところ使いづらい。

いろいろ試した結果、.mbedignoreでビルド時間の短縮に成功した。しかし、毎回手でこれを行うが面倒なので PlatformIOのextra_scriptsの機能を使用して、プロジェクト毎に簡単に使用できるようにしてみた。

.mbedignoreファイル

まず、.mbedignoreファイルについて説明する。

mbed osのソースツリーに配置することで、使用しない機能のソースファイルをビルドから除外することができる。

ただしソースツリーのルート直下に配置された.mbedignoreは無視されてしまう。

.mbedignoreの内容を正しく適用するにはその一段下のディレクトリにそれぞれ配置する必要がある。

.
├── TESTS
├── TEST_APPS
├── UNITTESTS
├── cmsis
├── components
├── docs
├── drivers
├── events
├── features
├── hal
├── platform
├── platformio
├── rtos
├── targets
└── tools

例えば、features/cellular/*をビルド対象から外したい場合は、featuresディレクトリに次の内容で.mbedignoreを作成する必要がある。

cellular/*

extra_scripts

PlatformIOではmbedフレームワークのソースツリーは${HOME}/.platformio/packages/framework-mbedに格納される。

framework-mbedディレクトリは、そのユーザーが使用するすべてのPlatformIOのプロジェクトから参照されるので、プロジェクト毎に毎回調整する必要が出てくる。

少なくとも使用するボードによってビルド対象から外したい機能やドライバは変わってくるはずなので、これを毎回設定するのは面倒くさい。

そこで、pio-mbedignoreというリポジトリに、ヘルパスクリプトを作成した。

使い方

ヘルパスクリプトの使い方の流れは次のようになる。

  1. リポジトリから適当な場所にダウンロードする
  2. mbedignore.pyをPlatformIOの自分のプロジェクトのルートに配置する
  3. mbedignoreディレクトリを作成し、その下にfeaturesdriversなどの名前でファイルを作成する
  4. 作成したファイルに.mbedignoreの記述ルールに従って、無視したいファイルのパスを指定する
  5. platformio.iniを修正し、extra_scriptに設定する

具体的には次のようになる。

$ git clone https://github.com/mickey-happygolucky/pio-mbedignore.git
$ cp pio-mbedignore/mbedignore.py /path/to/pio_project
$ cd /path/to/pio_project
$ mkdir mbedignore

先程の例のようにcellularを除外するにはmbedignore/featuresを次の内容で作成する。

cellular/*

ビルド毎にmbedignore.pyが実行されるようにするために、platformio.iniに次の内容を追加する。

2020/3/6 追記1

extra_scripts =
  pre:mbedignore.py
  post:mbedignore.py

extra_scripts =
  post:mbedignore.py

これで、pio run毎に、必要に応じて、framework-mbed以下に.mbedignoreが配置され、指定したファイルをビルド対象から外すことができるようになる。

extra_scriptsのpost:指定にも指定しているのはゴミが残らないようにするためだが、これについては後述する。

pre:post:の仕様を勘違いしていた。platformio.iniでのpre/post指定は、extra_scriptsの実行タイミングをPlatformIOのビルドシステムのメインスクリプトを実行する前にするか、後ろにするかという指定。 env.AddPreAction()/env.AddPostAction()を使用するため、ターゲットシステムの情報が確定されてから実行したいのでpost:のみの設定が正解。

2020/3/6 追記1 終了

仕組み

PlatformIOではplatformio.iniextra_scriptspythonスクリプトのファイルを指定すると、platformioコマンド実行時に既存の処理をカスタムしたり、任意の処理を追加したりすることができる。

今回作成したスクリプトはそれを利用して、pio run毎に必要に応じてframework-mbedディレクトリに.mbedignoreを配置するようにしている。 実際には、実ファイルを作成するわけではなく、シンボリックリンクを作成している。

mbedignore.py

extra_scriptsではImport(env)を使用することで、platformio実行時の環境変数にアクセスしたり、 任意のタイミングで実行されるアクションを登録できるようになっている。

Import("env")
import os
import glob

srcs = glob.glob(os.getcwd() + '/mbedignore/*')
print(srcs)
dst_basedir = env.Split(env['PROJECT_PACKAGES_DIR'])[0] + '/framework-mbed'

def clean_mbedignore(source, target, env):
    print('Clean .mbedignore')

    for src in srcs:
        dst_dir = dst_basedir + '/' + os.path.basename(src)
        dst = dst_dir +'/.mbedignore'

        if os.path.exists(dst) == True:
            os.remove(dst)
            print('symlink deleted : ' + dst)


def mbedignore():
    for src in srcs:
        dst_dir = dst_basedir + '/' + os.path.basename(src)
        dst = dst_dir +'/.mbedignore'

        if os.path.exists(dst) == True:
            os.remove(dst)
            print('symlink deleted : ' + dst)

        if os.path.exists(dst_dir) == True:
            os.symlink(src, dst)
            print('symlink created : src = ' + src + '->' + dst)


mbedignore()
env.AddPostAction("checkprogsize", clean_mbedignore)

PlatformIOが知っているディレクトリやファイルなどは環境変数のようにアクセスできるので、 必要な情報はそこから取り出すようにしている。

2020/3/6 追記2

env.AddPreAction("buildprog", clean_mbedignore)の行で、このスクリプトによって作成されたシンボリックリンクを削除する処理を、 bulidprogターゲットの後に実行されるように登録している。

なぜかplatformio.inipre:指定した場合はここで設定した処理が実行されないので、このスクリプトpost:でも登録している。

ただし、このスクリプトの実行時にすでに.mbedignoreが存在している場合は、削除してから作り直すので post:の指定を忘れても実行に影響は無い。

修正前はenv.AddPreAction("buildprog", clean_mbedignore)としていたが、ビルド済みだった場合実行されないので、 env.AddPostAction("checkprogsize", clean_mbedignore)とした。修正後はcheckprogsizeターゲットの後に、このスクリプトによって作成されたシンボリックリンクを削除している。

2020/3/6 追記2 終了

実行時間比較

リポジトリのテスト用のmbedignoreファイルを使用して、 bluepill環境でpio runの実行時間を比較した。

どちらも実行前にcleanしてある。

スクリプト未使用時

real  2m23.069s
user    8m41.679s
sys 1m38.485s

スクリプト使用時

real  0m23.076s
user    1m56.642s
sys 0m16.316s

体感でも違いがわかるくらいには差が出ている。

まとめ

PlatforIOでmbedを使用するとビルド時間が長い。

.mbedignoreを使えば不要なコンパイルを避けビルド時間を短縮できるが、 具体的な使い方がの情報が少ないことと、意外と不親切。

今回はmbedignore.pyを作成し、簡単に.mbedignoreの恩恵を受けられるようにした。

PlatformIO AE-ATmegaを動かす

はじめに

秋月のATmega168/328マイコンボードキットをPlatformIOでLチカしてみる。

かなり昔に購入したので、使用するMCUATmega168P

プログラムの書き込みにはAVRISP mkIIを使用する。最近では純正品は入手が難しいっぽい。

PlatformIOの設定方法はここを参照。

プロジェクトの作成

作業用ディレクトリを次のように作成する。

$ mkdir -p ~/pio/ae-atmega168p
$ cd ~/pio/ae-atmega168p

このボードはPlatformIOでサポートするボードのうちdiecimilaatmega168に該当する。

$ pio init -b diecimilaatmega168

プログラムの作成

platformio.ini

純粋なArduino DiecimilaではMCUATmega168だが、今回使用するボードではATmega168Pであるため、設定を変更する必要がある。

またプログラムを書き込むためのプログラマAVRISP mkIIに設定する。

[env:diecimilaatmega168]
platform = atmelavr
board = diecimilaatmega168
framework = arduino

; change microcontroller
board_build.mcu = atmega168p

; change programmer to AVRISP mkII
upload_protocol = stk500v2
; each flag in a new line
upload_flags =
    -Pusb

main.cpp

src/main.cppを次の内容で作成する。

#include <Arduino.h>

void setup()
{
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop()
{
  digitalWrite(LED_BUILTIN, HIGH);
  delay(1000);
  digitalWrite(LED_BUILTIN, LOW);
  delay(1000);
}

一般的なArduinoのLチカのコード。

AVRISP mkIIとボード、PCを接続

PCとAVRISP mkIIをUSBケーブルで接続する。

AVRISP mkIIの6ピンケーブルを、ボード上のICSPのシルクがある3x2のピンヘッダに接続する。 ケーブルの赤いほうが1ピン。

ボードは別途給電が必要なので、USBか電源を接続する。

ビルドと書き込み

次のコマンドでファームウェアをビルドする。

$ pio run

次のコマンドで書き込む。外部のプログラマを使用するのでtargetの指定がuploadでは無いことに注意。

$ pio run -t program
Processing diecimilaatmega168 (platform: atmelavr; board: diecimilaatmega168; framework: arduino)
--------------------------------------------------------------------------------
Verbose mode can be enabled via `-v, --verbose` option

...(snip)...

avrdude: verifying ...
avrdude: 928 bytes of flash verified

avrdude: safemode: Fuses OK (E:F8, H:DD, L:FF)

avrdude done.  Thank you.

========================= [SUCCESS] Took 1.34 seconds =========================

書き込みが成功し、LEDがチカチカする。

まとめ

今回はATmega168Pを使用したが、新しいロットではATmega328Pが付属しているらしいので 適宜、MCU変更する必要がある。

また、最近ではAVRISP mkIIは純正品の入手が困難みたいなので、互換品(クローン製品など含む)を使用する場合、 別途何か手順が必要かもしれない。

ともあれ、PlatformIOでも問題なく動作させられることがわかった。

STM32F4Discovery向けのNuttXをClangでビルドする

はじめに

NuttXは次のボードについてClangでのビルドに対応している。

  • nucleo-f446re
  • nucleo-f4x1re
  • stm32f746g-disco

筆者的によく使用するSTM32F4DiscoveryもClangでビルドできないか試してみる。

今回はclang-9を使用している。

clangの設定方法はここを参照。

NuttXのビルドやSTMへの書き込みに必要なパッケージの設定はここを参照。

Nuttxのビルド環境の作成

ソースの取得

$ mkdir ~/nuttx
$ cd ~/nuttx
$ git clone https://bitbucket.org/nuttx/nuttx.git
$ git clone https://bitbucket.org/nuttx/apps.git
$ cd nuttx

Clang対応

Make.defsの修正

./boards/arm/stm32/stm32f4discovery/scripts/Make.defsを修正し、Clangでのビルドに対応する。

この修正はすでにClangのビルドに対応してある環境のものを参考にする。

diff --git a/boards/arm/stm32/stm32f4discovery/scripts/Make.defs b/boards/arm/stm32/stm32f4discovery/scripts/Make.defs
index 48179cb2a5..189670ae2d 100644
--- a/boards/arm/stm32/stm32f4discovery/scripts/Make.defs
+++ b/boards/arm/stm32/stm32f4discovery/scripts/Make.defs
@@ -55,9 +55,6 @@ else
   ARCHSCRIPT = -T$(TOPDIR)/boards/$(CONFIG_ARCH)/$(CONFIG_ARCH_CHIP)/$(CONFIG_ARCH_BOARD)/scripts/$(LDSCRIPT)
 endif
 
-CC = $(CROSSDEV)gcc
-CXX = $(CROSSDEV)g++
-CPP = $(CROSSDEV)gcc -E
 LD = $(CROSSDEV)ld
 STRIP = $(CROSSDEV)strip --strip-unneeded
 AR = $(ARCROSSDEV)ar rcs
@@ -65,7 +62,6 @@ NM = $(ARCROSSDEV)nm
 OBJCOPY = $(CROSSDEV)objcopy
 OBJDUMP = $(CROSSDEV)objdump
 
-ARCHCCVERSION = ${shell $(CC) -v 2>&1 | sed -n '/^gcc version/p' | sed -e 's/^gcc version \([0-9\.]\)/\1/g' -e 's/[-\ ].*//g' -e '1q'}
 ARCHCCMAJOR = ${shell echo $(ARCHCCVERSION) | cut -d'.' -f1}
 
 ifeq ($(CONFIG_DEBUG_SYMBOLS),y)
@@ -73,19 +69,41 @@ ifeq ($(CONFIG_DEBUG_SYMBOLS),y)
 endif
 
 ifneq ($(CONFIG_DEBUG_NOOPT),y)
-  ARCHOPTIMIZATION += $(MAXOPTIMIZATION) -fno-strict-aliasing -fno-strength-reduce -fomit-frame-pointer
+  ARCHOPTIMIZATION += $(MAXOPTIMIZATION) -fno-strict-aliasing -fomit-frame-pointer
 endif
 
 ARCHCFLAGS = -fno-builtin
-ARCHCXXFLAGS = -fno-builtin -fno-exceptions -fcheck-new -fno-rtti
+ARCHCXXFLAGS = -fno-builtin -fno-exceptions -fcheck-new
 ARCHWARNINGS = -Wall -Wstrict-prototypes -Wshadow -Wundef
 ARCHWARNINGSXX = -Wall -Wshadow -Wundef
 ARCHDEFINES =
 ARCHPICFLAGS = -fpic -msingle-pic-base -mpic-register=r10
 
-CFLAGS = $(ARCHCFLAGS) $(ARCHWARNINGS) $(ARCHOPTIMIZATION) $(ARCHCPUFLAGS) $(ARCHINCLUDES) $(ARCHDEFINES) $(EXTRADEFINES) -pipe -funwind-tables
+ifeq ($(CONFIG_ARMV7M_TOOLCHAIN_CLANGL),y)
+  ARCHCCVERSION = {shell $(CC) -v 2>&1 | sed -n '/clang version/p' | sed -e 's/.* clang version \([0-9\.]\)/\1/g' -e 's/[-\ ].*//g'}
+  HOSTCC = clang
+  CC = clang
+  CXX = clang++
+  CPP = clang -E
+  ARCHCFLAGS += -nostdlib -ffreestanding -target arm-none-eabi -march=armv7-m -mcpu=cortex-m4
+  ARCHCXXFLAGS += -nostdlib -ffreestanding -target arm-none-eabi -march=armv7-m -mcpu=cortex-m4 -DCONFIG_WCHAR_BUILTIN
+else
+  ARCHCCVERSION = ${shell $(CC) -v 2>&1 | sed -n '/^gcc version/p' | sed -e 's/^gcc version \([0-9\.]\)/\1/g' -e 's/[-\ ].*//g' -e '1q'}
+  HOSTCC = gcc
+  CC = $(CROSSDEV)gcc
+  CXX = $(CROSSDEV)g++
+  CPP = $(CROSSDEV)gcc -E
+  ARCHCFLAGS += -funwind-tables
+  ARCHCXXFLAGS += -fno-rtti -funwind-tables
+  ifneq ($(CONFIG_DEBUG_NOOPT),y)
+    ARCHOPTIMIZATION += -fno-strength-reduce
+  endif
+
+endif
+
+CFLAGS = $(ARCHCFLAGS) $(ARCHWARNINGS) $(ARCHOPTIMIZATION) $(ARCHCPUFLAGS) $(ARCHINCLUDES) $(ARCHDEFINES) $(EXTRADEFINES) -pipe
 CPICFLAGS = $(ARCHPICFLAGS) $(CFLAGS)
-CXXFLAGS = $(ARCHCXXFLAGS) $(ARCHWARNINGSXX) $(ARCHOPTIMIZATION) $(ARCHCPUFLAGS) $(ARCHXXINCLUDES) $(ARCHDEFINES) $(EXTRADEFINES) -pipe -funwind-tables
+CXXFLAGS = $(ARCHCXXFLAGS) $(ARCHWARNINGSXX) $(ARCHOPTIMIZATION) $(ARCHCPUFLAGS) $(ARCHXXINCLUDES) $(ARCHDEFINES) $(EXTRADEFINES) -pipe
 CXXPICFLAGS = $(ARCHPICFLAGS) $(CXXFLAGS)
 CPPFLAGS = $(ARCHINCLUDES) $(ARCHDEFINES) $(EXTRADEFINES)
 AFLAGS = $(CFLAGS) -D__ASSEMBLY__
@@ -131,7 +149,6 @@ ifeq ($(CONFIG_DEBUG_SYMBOLS),y)
 endif
 
 
-HOSTCC = gcc
 HOSTINCLUDES = -I.
 HOSTCFLAGS = -Wall -Wstrict-prototypes -Wshadow -Wundef -g -pipe
 HOSTLDFLAGS =

コンフィグレーション

次のコマンドを実行し、STM32F4Discovery向けに設定する。

$ tools/configure.sh -l stm32f4discovery:nsh

make menuconfigでメニューを表示する。

$ make menuconfig

/キーで検索画面を表示しclangを検索する。検索結果の画面で1を押すとメニューにジャンプできる。

f:id:mickey_happygolucky:20200109102202p:plain
clangの検索結果

Toolchain SelectionのメニューからGeneric Clang toolchain under Linuxを選択する。

f:id:mickey_happygolucky:20200109102254p:plain
ツールチェインの選択

変更を保存し、メニューを終了する。

ビルド

下記のコマンドでビルド。

$ make -j $(nproc)

ビルドにclangが使用されているか確認したい場合は次のようにする。

$ make SHELL='sh -x' -j $(nproc)

nuttx.binが生成されればOK。

書き込み

次のコマンドで書き込む

$ st-flash write nuttx.bin 0x8000000

接続

USB-TTL変換ケーブルで、次の3つの信号を接続する。

ケーブル側 STM32F4Discovery
GND GND
TXD PA3(USART2_RX)
RXD PA2(USART2_TX)

実行

minicomなどでコンソールを開き、ターゲットをリセットする。

NuttShell (NSH) NuttX-8.2
nsh> help
help usage:  help [-v] [<cmd>]

  [         cp        exec      kill      mount     rmdir     true
  ?         cmp       exit      ls        mv        set       uname
  basename  dirname   false     mb        mw        sh        umount
  break     dd        free      mkdir     ps        sleep     unset
  cat       df        help      mkrd      pwd       test      usleep
  cd        echo      hexdump   mh        rm        time      xd

Builtin Apps:
  hello  nsh
nsh>

nshが使用できればOK。

まとめ

すでにNuttX側にClangのサポートが取り込まれているため、少しの修正でSTM32F4Discovery向けの環境でもClangでビルドできた。