はじめに
先日Raspberry Pi 4でUbuntuデスクトップを動かして遊ぶでラズパイを触ってみたのですが,今度はこのラズパイをYocto Projectを使って動かしてみました.
Yoctoをはじめて使ってみたのですが,いい入門になったと思うので内容をまとめます.
Ycto Projectとは?
主には組込みおよびIoT向けのLinuxディストリビューションを作成するための仕組み・ツールが用意されています.柔軟にカスタマイズしながら組込みLinuxイメージをビルドすることができます.多くの組込みLinuxプロバイダで採用されているようです.
Yocto Project - Wikipedia
やってみたこと
手もとにあったRaspberry Pi 4をYoctoでビルドしたイメージで起動してみました.
- Raspberry Pi 4をターゲットにしてビルド
- 簡単なレシピを用意してHello world!
- Wi-FiでSSH接続できるように設定
- Pythonを追加して開発環境の構築
- ツールチェーンを作成してクロスコンパイル環境構築
今回の開発環境
YoctoのビルドはWSL2のUbuntu 20.04 LTSで行いました.ターゲットは前述のとおりRaspberry Pi 4です.
- Windows 10 Pro(20H2)
- Ubuntu 20.04 LTS(WSL2)
- Yocto Project(3.2 Gatesgarth)
- Raspberry Pi 4(4GB)
前準備
まずはWSL2をインストールします.ディストリビューションは今回はUbuntu 20.04 LTSを使っています.
手順は例えばこちらを参考にしてください.
https://docs.microsoft.com/ja-jp/windows/wsl/install-win10
環境が立ち上がったら,とりあえずパッケージの更新を行っておきます.
$ sudo apt update && sudo apt upgrade -y
次にYocto Project Quick Startを参考にして必要なパッケージのインストールを行います.
$ sudo apt install -y gawk wget git-core diffstat unzip texinfo gcc-multilib build-essential chrpath socat cpio python python3 python3-pip python3-pexpect xz-utils debianutils iputils-ping libsdl1.2-dev xterm
Raspberry Pi 4をターゲットにしてビルド
まずはPoky(リファレンス・ディストリビューション)をGitでクローンします.
バージョンはこの時点で最新のGatesgarth(Yocto 3.2)
を指定しています.
Releases - Yocto Project
$ mkdir ~/yocto && cd ~/yocto
$ git clone git://git.yoctoproject.org/poky
$ cd poky
$ git checkout -b gatesgarth-24.0.1 refs/tags/gatesgarth-24.0.1
次にRaspberry Pi用のBSPレイヤーを追加します.
OpenEmbedded Layer Indexにmeta-raspberrypi
というのがあるので,これをクローンします.ブランチはPokyに合わせてgatesgarth
を指定しています.
$ cd ~/yocto/poky
$ git clone git://git.yoctoproject.org/meta-raspberrypi
$ cd meta-raspberrypi
$ git checkout -b gatesgarth origin/gatesgarth
meta-raspberrypi
のREADMEに書かれている手順を参考にします.
Dependencies
This layer depends on:
* URI: git://git.yoctoproject.org/poky
- branch: master
- revision: HEAD
* URI: git://git.openembedded.org/meta-openembedded
- layers: meta-oe, meta-multimedia, meta-networking, meta-python
- branch: master
- revision: HEAD
Quick Start
1. source poky/oe-init-build-env rpi-build
2. Add this layer to bblayers.conf and the dependencies above
3. Set MACHINE in local.conf to one of the supported boards
4. bitbake core-image-base
5. Use bmaptool to copy the generated .wic.bz2 file to the SD card
6. Boot your RPI
まずはoe-init-build-env
を読み込んで環境をセットアップします.実行するとrpi-build
ディレクトリが作成されて移動しています.ビルドはこのディレクトリで行います.
$ cd ~/yocto/poky
$ source oe-init-build-env rpi-build
次にこのレイヤーの追加設定を行います.add-layer
するとconf/bblayers.conf
にmeta-raspberrypi
が追加されます.
$ bitbake-layers add-layer ../meta-raspberrypi/
$ cat conf/bblayers.conf
:
BBLAYERS ?= " \
/home/user/yocto/poky/meta \
/home/user/yocto/poky/meta-poky \
/home/user/yocto/poky/meta-yocto-bsp \
/home/user/yocto/poky/meta-raspberrypi \
"
次にREADMEに書かれている依存レイヤーも同様に追加します.こちらもブランチはgatesgarth
を指定します.またadd-layer
実行する順番によってエラーが出てしまうので,下記の順で追加を行ってください.
$ cd ~/yocto/poky
$ git clone git://git.openembedded.org/meta-openembedded
$ cd meta-openembedded
$ git checkout -b gatesgarth origin/gatesgarth
$ cd ~/yocto/poky/rpi-build
$ bitbake-layers add-layer ../meta-openembedded/meta-oe/
$ bitbake-layers add-layer ../meta-openembedded/meta-python/
$ bitbake-layers add-layer ../meta-openembedded/meta-multimedia/
$ bitbake-layers add-layer ../meta-openembedded/meta-networking/
$ cat conf/bblayers.conf
:
BBLAYERS ?= " \
/home/user/yocto/poky/meta \
/home/user/yocto/poky/meta-poky \
/home/user/yocto/poky/meta-yocto-bsp \
/home/user/yocto/poky/meta-raspberrypi \
/home/user/yocto/poky/meta-openembedded/meta-oe \
/home/user/yocto/poky/meta-openembedded/meta-python \
/home/user/yocto/poky/meta-openembedded/meta-multimedia \
/home/user/yocto/poky/meta-openembedded/meta-networking \
"
次にlocal.conf
のMACHINE
を設定します.指定するマシン名はこちらのMachinesを参考にしてください.今回は下記のように設定しています.
MACHINE ?= "raspberrypi4-64"
最後にWSL2を使った場合に下記のエラーがビルド時に出てしまいました.(VMwareにインストールしたUbuntu 18.04ではこのエラーは発生しませんでした.)Stack Overflowにあった対処で解決しました.
ERROR: Task (/home/user/yocto/poky/meta/recipes-kernel/linux-libc-headers/linux-libc-headers_5.8.bb:do_install) failed with exit code '134'
PSEUDO_IGNORE_PATHS_append = ",/run/"
ここまでで設定は完了です.bitbake
コマンドでビルドを実行します.ビルドにはかなり時間がかかりますし,PCもフルで動きます.気長に待ちましょう.
参考に自分のノートPC(CPU:Core i7-8550U,メモリ:16.0 GB)では3~4時間ほどかかりました.またSSDは50GBほど使用します.
$ cd ~/yocto/poky/rpi-build
$ bitbake core-image-base
またWSL2を使用している場合は下記の警告が表示されます.対処はこちらの「6. Optimize your WSLv2 storage often」参照してください.
WARNING: You are running bitbake under WSLv2, this works properly but you should optimize your VHDX file eventually to avoid running out of storage space
ビルドが完了するとrpi-build/tmp/deploy/images/raspberrypi4-64
にOSイメージができあがっています.
bmaptool
コマンドを使ってcore-image-base-raspberrypi4-64.wic.bz2
をSDカードに書き込みます.ただ今回使用しているWSL2環境ではSDカードのデバイス名をどのように指定するのかが分かりませんでした.
$ cd ~/yocto/poky/rpi-build/tmp/deploy/images/raspberrypi4-64
$ sudo apt install -y bmap-tools
$ sudo bmaptool copy core-image-base-raspberrypi4-64.wic.bz2 /dev/sdX # SDカードのデバイス名をどのように指定できるのか不明
そこで今回はwic.bz2を解凍してからWindows上でRaspberry Pi Imagerを使ってSDカードに書き込みました.
$ cd ~/yocto/poky/rpi-build/tmp/deploy/images/raspberrypi4-64
$ cp core-image-base-raspberrypi4-64.wic.bz2 /tmp/. # シンボリックリンクになっているので適当な場所にコピー
$ bunzip2 /tmp/core-image-base-raspberrypi4-64.wic.bz2
$ mv /tmp/core-image-base-raspberrypi4-64.wic /mnt/c/Users/user/Downloads/. # Windows側の適当なディレクトリに移動
Operating SystemでUse customからcore-image-base-raspberrypi4-64.wic
を選択してSDカードに書き込みます.
他にもEtcherやDD for Windowsなどで書き込んでも問題ないと思います.
作成したSDカードをラズパイに挿して電源を入れるとOSが起動します.
コンソールへの出力が止まってEnterキーを押すとlog in:
と表示されるのでroot
でログインします.パスワードはありません.
成功すれば最小限の環境ですがしっかりとターミナルで操作が可能です.
raspberrypi4-64 log in: root
root@raspberrypi4-64:~# uname -mnr
raspberrypi4-64 5.4.72-v8 aarch64
root@raspberrypi4-64:~# cat /etc/issue
Poky (Yocto Project Reference Distro) 3.2.1 \n \l
簡単なレシピを作成してHello world!
次は自前の簡単なソースコードを書いて,コンパイルしてできた実行バイナリをインストールする手順をまとめます.
下記が参考になります.
Creating a General Layer Using the bitbake-layers Script
まずはレイヤーを作成します.下記のコマンドを実行するとmeta-hello
ディレクトリとその中に雛形が作成されます.
$ cd ~/yocto/poky
$ bitbake-layers create-layer meta-hello
$ ls meta-hello/
COPYING.MIT README conf recipes-example
続けて作成したレイヤーの追加設定を行います.
$ cd ~/yocto/poky/rpi-build
$ bitbake-layers add-layer ../meta-hello/
$ cat conf/bblayers.conf
:
BBLAYERS ?= " \
/home/user/yocto/poky/meta \
/home/user/yocto/poky/meta-poky \
/home/user/yocto/poky/meta-yocto-bsp \
/home/user/yocto/poky/meta-raspberrypi \
/home/user/yocto/poky/meta-openembedded/meta-oe \
/home/user/yocto/poky/meta-openembedded/meta-python \
/home/user/yocto/poky/meta-openembedded/meta-multimedia \
/home/user/yocto/poky/meta-openembedded/meta-networking \
/home/user/yocto/poky/meta-hello \
次にソースコードを用意します.下記のようにfiles
ディレクトリを作成しソースコードを配置します.
$ cd ~/yocto/poky/meta-hello
$ mkdir -p recipes-hello/hello && cd recipes-hello/hello
$ mkdir files && cd files
$ vim hello.c
用意したのはC言語のHello worldです.
#include <stdio.h>
int main(int argc, char const *argv[])
{
printf("Hello, world!\n");
return 0;
}
次にレシピ(パッケージのビルドのために使用されるメタデータ)を作成します.
$ cd ~/yocto/poky/meta-hello/recipes-hello/hello
$ vim hello_1.0.bb
レシピファイルの書き方はこちらが参考になります.
Single .c File Package (Hello World!)
ただし,このままではビルドに失敗するのでTARGET_CC_ARCH += "${LDFLAGS}"
を追加します.
Default Linker Hash Style Changed
またLIC_FILES_CHKSUM
のmd5
が分からない場合は空白にしておくと,ビルド時に正しい値がエラーで表示されます.
SUMMARY = "Simple helloworld application"
SECTION = "hello"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
TARGET_CC_ARCH += "${LDFLAGS}"
SRC_URI = "file://hello.c"
S = "${WORKDIR}"
do_compile() {
${CC} hello.c -o hello
}
do_install() {
install -d ${D}${bindir}
install -m 0755 hello ${D}${bindir}
}
最終的に次のようなディレクトリ・ファイル構成になります.
meta-hello/
├── COPYING.MIT
├── README
├── conf
│ └── layer.conf
├── recipes-example
│ └── example
│ └── example_0.1.bb
└── recipes-hello
└── hello
├── files
│ └── hello.c
└── hello_1.0.bb
最後にlocal.conf
のIMAGE_INSTALL_append
でイメージの追加を指定します.
IMAGE_INSTALL_append = " hello" # 先頭の空白文字が必要なので注意
ここまで準備ができたらビルドを行います.
$ cd ~/yocto/poky/rpi-build/
$ bitbake core-image-base
できあがったOSイメージをSDカードに書き込んでラズパイで起動します.ログインしてhello
コマンドが実行できることを確認します.うまくいくとHello, world!
が表示されます.
raspberrypi4-64 log in: root
root@raspberrypi4-64:~# hello
Hello, world!
Wi-FiでSSH接続できるように設定
次はWi-Fi経由でSSH接続できるように設定します.
ここまでの環境で,ip
コマンドで確認するとwlan0
は認識されているようです.
root@raspberrypi4-64:~# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc mq qlen 1000
link/ether dc:a6:32:b8:2b:4a brd ff:ff:ff:ff:ff:ff
3: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP8000> mtu 1500 qdisc pfifo_fast qlen 1000
link/ether dc:a6:32:b8:2b:4b brd ff:ff:ff:ff:ff:ff
Wi-Fi接続を行うにはConnManを使用するのが簡単そうです.local.confでイメージの追加設定を行います.
またSSH接続を行うためにOpenSSHの追加を行います.こちらを参考にしてIMAGE_FEATURES
に指定するとよさそうです.
IMAGE_INSTALL_append = " connman connman-client"
IMAGE_FEATURES_append = " ssh-server-openssh"
ここまででbitbake core-image-base
してOSイメージをビルドして,SDカードからラズパイを起動します.
connmanctl
コマンドを使って次の手順でWi-Fi接続設定を行います.
root@raspberrypi4-64:~# connmanctl
connmanctl> enable wifi
connmanctl> scan wifi
connmanctl> services
Buffalo-A-0F20 wifi_dca632b82b4b_42756666616c6f2d412d30463230_managed_psk
Buffalo-G-0F20 wifi_dca632b82b4b_42756666616c6f2d472d30463230_managed_psk
WARPSTAR-D56FD6 wifi_dca632b82b4b_57415250535441522d443536464436_managed_psk
connmanctl> agent on
connmanctl> connect wifi_dca632b82b4b_42756666616c6f2d412d30463230_managed_psk
Passphrase?
connmanctl> exit
パスフレーズの入力まで完了したらconnmanctl
を終了し,再度ip
コマンドで確認するとwlan0
にIPアドレスが割り振られています.
このIPアドレスにSSH接続を試すとrootでログインができるはずです.
$ ssh root@192.168.1.8 # ラズパイのwlan0に割当たっているIPアドレス
Last login: Sun Jan 10 02:06:14 2021 from 192.168.1.7
root@raspberrypi4-64:~#
SSH接続とは直接関係ないですが,手もとの開発環境でSFTPが使えると便利だったので追加しています.
ついでにrootのパスワード設定とpiユーザの新規作成も行っておきます.あわせてsudo
コマンドも追加します.
またOpenSSHの追加で調べているとsystemdを有効にする設定をよく見かけたのでこれも入れておきます.
IMAGE_INSTALL_append = " openssh-sftp-server sudo"
IMAGE_FEATURES_append = " ssh-server-openssh"
# user settings
INHERIT_append = " extrausers"
EXTRA_USERS_PARAMS = "useradd -P raspberry pi;usermod -P raspberry root;"
# systemd settings
DISTRO_FEATURES_append = " systemd"
VIRTUAL-RUNTIME_init_manager = "systemd"
DISTRO_FEATURES_BACKFILL_CONSIDERED = "sysvinit"
VIRTUAL-RUNTIME_initscripts = ""
IMX_DEFAULT_DISTRO_FEATURES_append = " systemd"
ここまででビルドして,ラズパイで起動してSSH接続を確認します.(今回のビルドはまた時間がそこそこかかります.)
rootの他にpiユーザでもSSHでログインできます.パスワードは新規に設定したものを使用します.(この例だとraspberry
)
piユーザでsudoを使えるようにするにはrootからvisudo
コマンドで設定します.
root ALL=(ALL) ALL
pi ALL=(ALL) ALL # ここを追記
Pythonを追加して開発環境の構築
開発環境としてPythonを追加します.ラズパイを使っているのでrpi-gpio
を追加してGPIOの制御を簡単に行ってみます.また手元にLEDなどがなかったので,raspi-gpio
というGPIOを制御するコマンドを使って確認をします.
IMAGE_INSTALL_append = " python3 python3-pip rpi-gpio raspi-gpio"
OSイメージをビルドしてラズパイで起動します.rootユーザで確認をしています.
Pythonとpipが使用できるか確認してみます.下記のように出力されてrpi-gpio
が追加されていることも確認できます.
# python3 --version
Python 3.8.5
# python3 -m pip list
Package Version
---------- -------
pip 20.0.2
RPi.GPIO 0.7.0
setuptools 49.6.0
raspi-gpio
コマンドが使えることも確認しておきます.helpで簡単な使い方が確認できると思います.
GPIO1番ピンはデフォルトだと入力でプルアップになっているようです.
# raspi-gpio help
# raspi-gpio get 1
GPIO 1: level=1 fsel=0 func=INPUT pull=UP
PythonでGPIOを制御する簡単なサンプルコードを書きます.GPIO1番ピンを出力設定にして,1秒ごとにON/OFFを切り替えます.
import time
import RPi.GPIO as GPIO
def main():
pin_num = 1
GPIO.setmode(GPIO.BCM)
GPIO.setup(pin_num, GPIO.OUT)
for i in range(10):
GPIO.output(pin_num, i % 2)
print(f'GPIO {"ON" if i % 2 else "OFF"}')
time.sleep(1)
GPIO.cleanup(pin_num)
if __name__ == '__main__':
main()
上記のPythonコードを実行しながらraspi-gpio
を繰り返し実行してGPIO1番ピンを確認すると,funcが出力に切り替わってlevelが0/1と切り替わっている様子が確認できます.
# python3 gpio.py # 実行中に下記を別セッションで確認
# raspi-gpio get 1
GPIO 1: level=0 fsel=1 func=OUTPUT pull=NONE
# raspi-gpio get 1
GPIO 1: level=1 fsel=1 func=OUTPUT pull=NONE
# raspi-gpio get 1
GPIO 1: level=0 fsel=1 func=OUTPUT pull=NONE
# raspi-gpio get 1
GPIO 1: level=1 fsel=1 func=OUTPUT pull=NONE
GPIOをデバイスファイルから制御する方法
raspi-gpioを使わずにGPIOをデバイスファイルから制御する方法についても簡単にまとめておきます.
下記の手順で確認するとGPIO1番ピンがデフォルトでは入力になっているのが分かります.この方法でもPythonのサンプルコードを実行して出力に切り替わってON/OFFしている様子が確認できます.
# cd /sys/class/gpio
# ls
export gpiochip0 gpiochip504 unexport
# echo 1 > export
# ls
export gpio1 gpiochip0 gpiochip504 unexport
# cd gpio1
# ls
active_low device direction edge power subsystem uevent value
# cat active_low direction value
0
in
1
ツールチェーンを作成してクロスコンパイル環境構築
最後にツールチェーンをビルドして,ローカルのPCにクロスコンパイル環境を構築します.
今回はC++のコードをビルドして動かしたのですが,実行するとlibstdc++.so.6
が見つからないというエラーが出ました.C++標準ライブラリがデフォルトでは入っていなさそうなので,追加の設定を行います.
IMAGE_INSTALL_append = " libstdc++"
TOOLCHAIN_TARGET_TASK_append = " libstdc++-staticdev"
ここまでの内容をまとめると,最終的にlocal.confは次の内容になっています.
MACHINE ?= "raspberrypi4-64"
# for WSL2
PSEUDO_IGNORE_PATHS_append = ",/run/"
# image
IMAGE_INSTALL_append = " hello connman connman-client openssh-sftp-server sudo python3 python3-pip rpi-gpio raspi-gpio libstdc++"
IMAGE_FEATURES_append = " ssh-server-openssh"
# user settings
INHERIT_append = " extrausers"
EXTRA_USERS_PARAMS = "useradd -P raspberry pi;usermod -P raspberry root;"
# systemd settings
DISTRO_FEATURES_append = " systemd"
VIRTUAL-RUNTIME_init_manager = "systemd"
DISTRO_FEATURES_BACKFILL_CONSIDERED = "sysvinit"
VIRTUAL-RUNTIME_initscripts = ""
IMX_DEFAULT_DISTRO_FEATURES_append = " systemd"
# for c++
TOOLCHAIN_TARGET_TASK_append = " libstdc++-staticdev"
local.confを書き終えたらOSイメージとツールチェーンのビルドを行います.ツールチェーンのビルドにはまた時間がかかりますのでゆっくりと待ちましょう.またSSDもこれまでの手順で80GBほど使用しますので注意してください.
$ cd ~/yocto/poky/rpi-build
$ bitbake core-image-base
$ bitbake core-image-base -c populate_sdk # ツールチェーンのビルド
ビルドが完了するとrpi-build/tmp/deploy/sdk
にツールチェーンができあがっています.この中の.shファイルを実行するとインストールを行うことができます.インストール先はデフォルトでは/opt/poky/3.2.1
になっています.
environment-setup-cortexa72-poky-linux
を読み込むとクロスコンパイル環境を使うことができます.
$ cd ~/yocto/poky/rpi-build/tmp/deploy/sdk
$ sudo ./poky-glibc-x86_64-core-image-base-cortexa72-raspberrypi4-64-toolchain-3.2.1.sh
$ source /opt/poky/3.2.1/environment-setup-cortexa72-poky-linux
今回はC++のHello world!をCMakeを使ってビルドしてラズパイで動かしてみます.
$ mkdir ~/hello && cd ~/hello
$ vim hello.cpp
$ vim CMakeLists.txt
$ mkdir build && cd build
$ cmake ..
$ make
今回使用したhello.cpp
とCMakeLists.txt
は次の内容です.
#include <iostream>
int main(int argc, char const *argv[])
{
std::cout << "Hello, world!" << std::endl;
return 0;
}
cmake_minimum_required(VERSION 3.0.0)
project(hello VERSION 0.1.0)
set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "limited configs" FORCE)
add_executable(hello
hello.cpp
)
target_compile_features(hello
PRIVATE cxx_std_17
)
ビルドしてできあがった実行バイナリをreadelf
コマンドで確認してみます.MachineがAArch64
になっており,クロスコンパイルができていることが分かります.(今回ビルドを実施したPCはx86_64
)
$ readelf -h hello
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: DYN (Shared object file)
Machine: AArch64
Version: 0x1
:
$ uname -m
x86_64
実行バイナリをscp
コマンドでラズパイにコピーして動かすと,Hello, world!
が表示されます.
$ scp ./hello root@192.168.1.8:/home/root/.
# ~/hello
Hello, world!
おわりに
今回はRaspberry Pi 4をターゲットにして,Yocto Projectを使ってLinuxをビルドし実際に動かしました.基本的な手順についてある程度は確認することができたと思います.Yoctoは日本語の情報が少ないのと,とにかくビルドに時間がかかってしまうため,初心者にはとっつきにくいところがあると感じました.この記事は,はじめてYoctoを触ってみる人にとって,ちょうどいいチュートリアルになると思います.Yoctoをはじめて触ってみる人たちの参考にしてもらえるとありがたいです.
参考にした書籍
最後に参考にした書籍の紹介です.とっかかりはYocto Project入門を参考にさせてもらいました.Yoctoをはじめて触ったので大変助かりました.今回まとめている内容も,特に前半はこちらの書籍の内容を参考にして自分の開発環境に合わせて動かし,また追加で調べながらまとめていきました.