みつきんのメモ

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

yocto raspberrypi3でNFSブート

raspberrypi3のルートファイルシステムNFSでマウントする。

ベース環境の取得

$ git clone git://git.yoctoproject.org/poky.git -b pyro
$ cd poky
$ git clone git://git.yoctoproject.org/meta-raspberrypi -b pyro

使用するのはpyroブランチ。

oe-init-build-envの読み込み

下記を実行する。

$ cd ~/rpi3
$ source poky/oe-init-build-env

これにより、クロスコンパイルに必要な環境変数が設定され、ビルドディレクトリに移動される。

meta-raspberrypiをビルド対象に追加

次のコマンドを実行する。

$ bitbake-layers add-layer ../poky/meta-raspberrypi

local.confの修正

マシンの設定と、カーネルのブートパラメータを設定する。

MACHINE ?= "raspberrypi3"

CMDLINE_remove = "root=/dev/mmcblk0p2 rootfstype=ext4 rootwait"
CMDLINE_append = " ip=192.168.1.100 root=/dev/nfs nfsroot=192.168.1.10:/exports rw rootwait"

動作確認

bitbakeの実行

$ bitbake rpi-basic-image

SDカードの作成

本来はブートパーティションだけで良いが、手順が複雑になるので、通常通りSDカードイメージをddで書き込む

$ sudo dd if=./tmp/deploy/images/raspberrypi3/core-image-sato-raspberrypi3.rpi-sdimg of=/dev/sdb bs=100M

nfsサーバ

インストール

$ sudo apt-get install -y nfs-kernel-server

公開ディレクトリの設定

tftpサーバとは異なり、デフォルトの公開ディレクトリは設定されていないため、この設定をおこなう。

/etc/exportsに下記の内容を追加する。

/exports 192.168.1.0/255.255.255.0(rw,no_root_squash,subtree_check)

/exportsを作成する。

$ sudo mkdir /exports

NFSデーモンの再起動

変更した設定を読み込むためにNFSデーモンを再起動

$ sudo systemctl restart nfs-kernel-server

ファイルの展開

bitbakeで生成されたrootfsのイメージを/exportsに展開する。

$ sudo tar xvf ./tmp/deploy/images/raspberrypi3/rpi-basic-image-raspberrypi3.tar.bz2 -C /exports/

ラズパイ起動

ここまでやってSDカードをraspberrypi3に挿入して起動すると、ルートファイルをNFSでマウントした状態で起動される。

yoctoで作ったLinuxでQt5 アプリケーションのフォントが表示されない

yoctoでmeta-qt5を組み込んで、Qt5アプリケーションを起動させた時に、次のようなエラーメッセージが表示され、文字が正しく表示されない問題が発生した。

QFontDatabase: Cannot find font directory /usr/lib/fonts.
Note that Qt no longer ships fonts. Deploy some (from http://dejavu-fonts.org for example) or switch to fontconfig.
QFontDatabase: Cannot find font directory /usr/lib/fonts.
Note that Qt no longer ships fonts. Deploy some (from http://dejavu-fonts.org for example) or switch to fontconfig.
QFontDatabase: Cannot find font directory /usr/lib/fonts.
Note that Qt no longer ships fonts. Deploy some (from http://dejavu-fonts.org for example) or switch to fontconfig.
QFontDatabase: Cannot find font directory /usr/lib/fonts.
Note that Qt no longer ships fonts. Deploy some (from http://dejavu-fonts.org for example) or switch to fontconfig.
QFontDatabase: Cannot find font directory /usr/lib/fonts.
Note that Qt no longer ships fonts. Deploy some (from http://dejavu-fonts.org for example) or switch to fontconfig

local.confでは次のように定義してあるため、ttf-dejavu関連のパッケージはインストールしてある。

# fonts
IMAGE_INSTALL_append = " \
             ttf-dejavu-common \
             ttf-dejavu-sans \
             ttf-dejavu-serif \
"

これらのパッケージは、/usr/share/fonts/truetype以下にttfファイルをインストールするようになっているが、 Qt5が/usr/lib/fontsにフォントファイルが配置されていることを期待しているため、フォントを見つけられないということらしい。

対策

ここの一連のスレッドを読むと、同じ問題に引っかかっている人が、 フォントのレシピにbbappendを作成して、インストール先を変更することで、問題を解決している。

#
# This method from 
#  https://lists.yoctoproject.org/pipermail/yocto/2016-October/032453.html
#

do_install() {
    install -d ${D}${libdir}/fonts/
    find ./ -name '*.tt[cf]' -exec install -m 0644 {} ${D}${libdir}/fonts/ \;
    install -d ${D}${sysconfdir}/fonts/conf.d/
    install -m 0644 ${WORKDIR}/30-dejavu-aliases.conf ${D}${sysconfdir}/fonts/conf.d/
}

FILES_${PN}-sans            = "${libdir}/fonts/DejaVuSans.ttf ${libdir}/fonts/DejaVuSans-*.ttf"
FILES_${PN}-sans-mono       = "${libdir}/fonts/DejaVuSansMono*.ttf"
FILES_${PN}-sans-condensed  = "${libdir}/fonts/DejaVuSansCondensed*.ttf"
FILES_${PN}-serif           = "${libdir}/fonts/DejaVuSerif.ttf ${libdir}/fonts/DejaVuSerif-*.ttf"
FILES_${PN}-serif-condensed = "${libdir}/fonts/DejaVuSerifCondensed*.ttf"
FILES_${PN}-mathtexgyre     = "${libdir}/fonts/DejaVuMathTeXGyre.ttf"

bbappendは次のコマンドで作成すると適切な場所に空のファイルを作成しエディタで開いてくれるため簡単。

$ recipetool newappend meta-local ttf-dejavu -e

ここでは予めmeta-localというレイヤを作ってあることを前提としている。

meta-localも次のように作ることができる。

$ yocto-layer create local 10
$ bitbake-layers add-layer ./meta-local

ディレクトリ構成は手抜き。

yocto core-image-minimalなどで自動ログイン

core-image-minimalなどのコンソール環境で自動ログインする。

昔はIMAGE_FEATURESにautologinとか入れれば、実現できたらしいが 機能の実現に使用していたパッケージがメンテナンスされてなかったらしく、 この機能が削除されてしまったらしい。

基本的には、tty1のgettyのパラメータを変更することで実現できるが、sysvinitとsystemdで修正するファイルが異なる。

sysvinitでの自動ログイン

sysvinitでは/etc/inittabを変更することで、自動ログインを実行する。 sysvinitではデフォルトでbusyboxのgettyが使用されるので、autologinが使用できないので、 agettyやmingettyを入れる必要がある。

local.confを修正して、agettyを入れる。

IMAGE_INSTALL_append = " util-linux-agetty"

その後、core-image-minimalのイメージにログインして、次のコマンドを実行する。

sed -i 's|/sbin/getty|/sbin/agetty -a root --noclear|' /etc/inittab

再起動後、自動的にrootでログインされるようになる。

systemdでの自動ログイン

ドロップインファイルを作成し、agettyのパラメータを変更する。

$ mkdir /etc/systemd/system/getty@tty1.service.d
$ cat << 'EOF' > /etc/systemd/system/getty@tty1.service.d/autologin.conf
[Service]
ExecStart=
ExecStart=-/sbin/agetty -a root --noclear %I $TERM
EOF

yoctoへの組み込み

meta-autologinの作成

$ yocto-layer create autologin 10
$ mkdir -p meta-autologin/classes
$ mv ./meta-autologin ../poky
$ bitbake-layers add-layer ../poky/meta-autologin

autologin.bbclassの作成

bbclassを使用してROOTFS_POSTPROCESS_COMMAND、イメージ作成時に処理をフックする。

meta-autologin/classes/autologin.bbclassを作成

$ emacs ../poky/meta-autologin/classes/autologin.bbclass

次の内容を記述

auto_login_sysvinit() {
    sed -i 's|/sbin/getty|/sbin/agetty -a root --noclear|' \
        ${IMAGE_ROOTFS}/etc/inittab
}

auto_login_systemd() {
    mkdir ${IMAGE_ROOTFS}/etc/systemd/system/getty@tty1.service.d
    cat << 'EOF' > ${IMAGE_ROOTFS}/etc/systemd/system/getty@tty1.service.d/autologin.conf
[Service]
ExecStart=
ExecStart=-/sbin/agetty -a root --noclear %I $TERM
EOF
}

ROOTFS_POSTPROCESS_COMMAND += "${@bb.utils.contains('DISTRO_FEATURES', 'systemd', 'auto_login_systemd;', 'auto_login_sysvinit;', d)}"
RRECOMMENDS_base-files_append = "${@bb.utils.contains('DISTRO_FEATURES', 'systemd', '', ' util-linux-agetty', d)}"

local.confの修正

local.confに次の行を追加

INHERIT += "autologin"

ビルド

$ bitbake core-image-minimal

ラズベリーパイなら

$ bitbake rpi-basic-image

これで、rootとして自動ログインされるようになる。

ROCK64をyoctoで立ち上げ

秋月でROCK64ボードを購入した。 今回はコスト優先でメモリ1GBのモデルにした。

このボードはラズパイの40ピンと互換のGPIOヘッダが搭載されているが、電源コネクタがマイクロUSBではなくDCプラグとなっている。 ただ、このDCプラグが曲者で、秋月で売っている2.1mmのコネクタがささらない為、別途用意する必要がある。

秋月では、ラズベリーパイ用のユニバーサル基板のHATを使用して、2.1mmのDCプラグをつける方法を紹介しているが、 ただ単に動作を見たいだけなら、USBシリアル変換ケーブルなどで、USBの5VをGPIOヘッダの電源ピンにぶち込むのが一番簡単だと思われる。

yoctoのビルド方法はここを参考に作業した。

作業ディレクト

~/yocto/rock64-pyroで作業する

$ mkdir ~/yocto/rock64-pyro
$ cd ~/yocto/rock64-pyro

ソースの取得

$ repo init --repo-url=https://github.com/rockchip-linux/repo -u https://github.com/rockchip-linux/manifests -b yocto -m pyro.xml
$ repo sync

環境変数設定

$ MACHINE=evb-rk3328 DISTRO=rk-wayland . ./setup-environment -b build

ビルド

$ bitbake rk-image-base

書き込み

システム全体

$ cd ~/yocto/rock64-pyro
$ ./flash_tool.sh -c rk3288 -d /dev/sdb -p system -i ./build/tmp/deploy/images/evb-rk3328/rk-image-base-evb-rk3328-gpt.img

rootfsのみ

$ cd ~/yocto/rock64-pyro
$ ../flash_tool.sh -c rk3288 -d /dev/sdb -p rootfs -i ./build/tmp/deploy/images/evb-rk3328/rk-image-base-evb-rk3328.ext4

カーネルのみ

$ cd ~/yocto/rock64-pyro
$ ../flash_tool.sh -c rk3288 -d /dev/sdb -p rootfs -i ./build/tmp/deploy/images/evb-rk3328/rk-image-base-evb-rk3328-boot.img

ddでの書き込み

システム全体の書き込みであれば、ddでもできる。

$ cd ~/yocto/rock64-pyro/build
$ sudo dd if=tmp/deploy/images/evb-rk3328/rk-image-base-evb-rk3328-gpt.img of=/dev/sdb bs=100M

ビルドディレクトリで実行できるので、こちらの方が便利かもしれない。

シリアルコンソール

この手のボードでよく使われるボーレートは115200だが、このボードは1500000となっている。

$ minicom -b 1500000 -D /dev/ttyUSB0

電源投入

このボードは電源ボタンが実装されている。 USBケーブルの電源を接続しても反応がない場合は、電源ボタンを2〜3秒長押しすると良い。

yocto raspberrypi3でA2DP レシーバ

スマートフォンなどの音源を、Bluetooth経由でラズベリーパイ3からならす。 OSはいつもどおりyoctoprojectを使用して作成。

BluetoothではA2DPを使用し、sinkとなるようにする。 実際に音を鳴らすのはpulseaudioで、ラズベリーパイ3が他の端末からオーディオに見えるようにするための設定も pulseaudioのbluetooth-discoverモジュールで行う。

そのため、bluez5自体の設定ファイルはいらない。

ラズベリーパイ3上でBluetoothで接続された音源と音を鳴らすためのデバイスをloopbackしたり、経路を作る必要があり、 よそのサイトではこの部分にudevのルールを書いたりpythonスクリプトを書いたりしているのを見かけたが、 これもpulseaudioのbluetooth-policyモジュールで行うため今回は不要。

使用するyoctoprojectのバージョンはpyro(2.3) すでにrocko(2.4)が出ているが、今回は古いので行く。

環境構築

ソースの取得

$ git clone git://git.yoctoproject.org/poky.git -b pyro
$ git clone git://git.openembedded.org/meta-openembedded -b pyro
$ git clone git://git.yoctoproject.org/meta-raspberrypi -b pyro

環境変数設定

環境変数の読み込みとビルドディレクトリの作成

$ cd ..
$ source poky/oe-init-build-env

ビルド対象のレイヤを追加

$ bitbake-layers add-layer ../poky/meta-raspberrypi
$ bitbake-layers add-layer ../poky/meta-openembedded/meta-oe
$ bitbake-layers add-layer ../poky/meta-openembedded/meta-python

local.confの変更

下記をlocal.confに追加する

MACHINE = "raspberrypi3"

# systemd
DISTRO_FEATURES_append = " systemd"
VIRTUAL-RUNTIME_init_manager = "systemd"
DISTRO_FEATURES_BACKFILL_CONSIDERED = "sysvinit"
VIRTUAL-RUNTIME_initscripts = ""
IMAGE_INSTALL_append = " systemd-bash-completion \
             "

# pulseaudio
DISTRO_FEATURES_append = " pulseaudio pam"
IMAGE_INSTALL_append = " pulseaudio \
                 pulseaudio-server \
             pulseaudio-misc \
             pulseaudio-module-systemd-login \
             pulseaudio-module-bluez5-discover \
             pulseaudio-module-bluez5-device \
             pulseaudio-module-bluetooth-policy \
             pulseaudio-module-bluetooth-discover \
             pulseaudio-module-loopback \
             pulseaudio-bash-completion \
"

# bluez5
IMAGE_INSTALL_append = " bluez5"
IMAGE_INSTALL_append = " bluez5-testtools"

# connman
IMAGE_INSTALL_append = " connman connman-client"

# avahi
IMAGE_INSTALL_append = " avahi-daemon"

FETCHCMD_wget = "/usr/bin/env wget -t 2 -T 3000 -nv --passive-ftp --no-check-certificate"

# automount
DISTRO_FEATURES_append = " automount"

# util-linux 
IMAGE_INSTALL_append = " util-linux-bash-completion \
                 util-linux-lsblk"

# Enable UART for debug console
ENABLE_UART = "1"

# ALSA
IMAGE_INSTALL_append = " alsa-utils \
             alsa-utils-speakertest \
"

# sudo
IMAGE_INSTALL_append = " sudo"

若干不要なパッケージや設定も含まれているが気にしない。

pulseaudioのbluetoothモジュールのロード設定

/etc/pulse/system.paに下記を追加

### Automatically load driver modules for Bluetooth hardware
.ifexists module-bluetooth-policy.so
load-module module-bluetooth-policy
.endif

.ifexists module-bluetooth-discover.so
load-module module-bluetooth-discover
.endif

今回のケースでは、module-bluetooth-discoverはBluetoothの設定を自動化し module-bluetooth-policyはa2dp_sourceとsinkのloopbackを自動化する。

pulseaudioのシステムサーバ化

/etc/systemd/system/pulseaudio.serviceを下記の内容で作成

[Unit]
Description=PulseAudio system server

[Service]
Type=notify
ExecStart=/usr/bin/pulseaudio --daemonize=no --system --realtime --log-target=journal

[Install]
WantedBy=multi-user.target

サービスの登録、起動

$ systemctl --system enable pulseaudio.service 
$ systemctl --system start pulseaudio.service 

dbus設定

/etc/dbus-1/system.d/pulseaudio-system.confに下記の行を追加

 <busconfig>
                                                                          
   <!-- System-wide PulseAudio runs as 'pulse' user. This fragment is
        not necessary for user PulseAudio instances. -->
                                                                          
   <policy user="pulse">
     <allow own="org.pulseaudio.Server"/>
+    <allow send_destination="org.bluez"/>
+    <allow send_interface="org.bluez.Manager"/>
   </policy>
                                                                     
 </busconfig> 

pactlの使い方

sudo でpulseユーザーを指定して実行する

$ sudo -u pulse pactl list short sinks

blueoothの設定

discoverableを有効にする。

$ connmanctl enable bluetooth
$ bluetoothctl
> power on
> discoverable on
> agent on
> quit

ここまででペアリング可能になる。

このままではAccessDeniedが発生し接続できないが、相手のデバイスをtrustすることで接続できるようになる。

自動化

bluetoothctlではバッチ処理に不向であることと、相手のデバイスをtrustする手順が煩雑になるため、 bluez5-testtoolsに含まれるスクリプトを使用し、ある程度自動化できるようにする。

まずはバグフィックス

$ cd /usr/lib/bluez/test/
$ find -type f | xargs sed -i 's/iteritems/items/g'

次のコマンドを実行する。

$ cd /usr/lib/bluez/test
$ ./test-adapter discoverable on
$ ./simple-agent -c NoInputNoOutput

simple-agentがtrustまで行ってくれるので、bluetoothctlの手順よりも多少は簡略化できる。 これでも、相手とのペアリングのタイミングでプロンプトがでるため、完全にヘッドレスでの運用はできない。 一度、接続が成功し相手の端末に接続設定が保存されてしまえば、ヘッドレス運用もできると思う。

その場合はシステム起動時にsimple-agentを起動し、 GPIOで物理ボタンなどをつけて、押されたタイミングでdiscoverable onを行うなど工夫が必要になる。

内蔵オーディオについて

ラズベリーパイ内蔵のオーディオを使用するためにはsnd_bcm2835ドライバを組み込む必要がある。

SDカードのブートパーティションにあるconfig.txtに下記を追加すると、ブート時にドライバが組み込まれるようになる。

dtparam=audio=on

まとめ

ここまでの設定を行った後で再起動してsimple-agentを立ち上げると、 スマートフォンipodなどとBluetoothで接続して、音源を鳴らすことが出来るようになる。

yoctoで日本語入力

今まで、yoctoで作成したLinuxでは日本語を表示できる環境はよく見かけたが、 meta-oeにはuimanthyのパッケージが存在するのに、日本語を入力できる環境を見たことがなかった。

今回、仕事がらみで日本語入力環境について調べたところ、uimのレシピに不具合があり、 正しい日本語入力環境が作成できなくなっていたことがわかった。 いろいろ修正した結果日本語が入力できる環境を作成することができたので、 これらの修正パッチをmeta-openembeddedのMLに投稿したところ、取り込んでもらえた。 そのため、masterブランチおよび次期バージョンではuim+anthyによる日本語入力環境が作成できるようになる。

ここではmasterブランチを使用し、qemu環境で日本語を入力できる環境を構築する。

poky及びmeta-openembeddedの取得

$ git clone git://git.yoctoproject.org/poky.git
$ cd poky
$ git clone git://git.openembedded.org/meta-openembedded

環境変数の設定

$ cd ../
$ source poky/oe-init-build-env build

これで自動的にbuildディレクトリへ移動する。

レイヤの追加

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

local.confの修正

IMAGE_INSTALL_append = " uim-xim uim-utils uim-common uim-gtk2.0 uim-gtk3 uim-anthy"
IMAGE_INSTALL_append = " ttf-vlgothic ttf-sazanami-gothic ttf-sazanami-mincho"
IMAGE_INSTALL_append = " setxkbmap"

IMAGE_LINGUAS ?= "ja-jp ja-jp.euc-jp"
GLIBC_GENERATE_LOCALES = "ja_JP.UTF-8 ja_JP.EUC-JP"

日本語入力に必要なパッケージと、日本語フォント、キーボードレイアウトの設定。 そしてロケールの設定を追加している。

bitbake

$ bitbake core-image-sato

QEMUの起動

buildディレクトリで次のコマンドを実行するとQEMUが起動する。

$ runqemu

日本語設定

そのままでは、日本語の表示び入力が有効化されていないので、~/.profileを以下の内容で作成し、rebootする。

export LC_ALL=ja_JP.UTF-8
export GTK_IM_MODULE="uim"
export XMODIFIERS="@im=uim-anthy"
uim-xim &
uim-toolbar-gtk-systray &
setxkbmap -layout jp

日本語入力

再起動後、キーボードも日本語配列になり、 「shift+スペース」で日本語入力ができるようになる。

きちんと試してはいないが、Qt系のアプリではおそらく効果はないと思う。(QT_IM_MODULEの環境変数を設定しても)

f:id:mickey_happygolucky:20170917053328p:plain
日本語入力の様子

これらのパッケージは、もちろんQEMU以外の実機でも動作する。

Ubuntu16.04でローカルのdebファイルを依存関係を解決しながらインストール(gdebi)

debファイルをインストール

例えばUbuntugoogle chromeなどをインストールする場合、debファイルをダウンロードしてきてインストールする必要がある。通常だと次のような感じでdpkgコマンドを使用する。

$ sudo dpkg -i ./google-chrome-stable_current_amd64.deb

この場合、インストールしたいパッケージが他のパッケージを必要としているなどの依存関係があったとしても、自動的に解決してはくれない。 aptでは自動的に依存するパッケージをインストールしてくれるが、ローカルにdebファイルをインストールすることができない。

gdebiコマンド

gdebiファイルを使用すると、ローカルのdebファイルをインストールするときに、aptのように依存関係を解決してくれる。

gdebiコマンドそのものはaptでインストールできる。

$ sudo apt install gdebi

gdebiコマンドでgoogle chromeをインストールする場合は次のようにする。

$ sudo gdebi ./google-chrome-stable_current_amd64.deb

便利。