びぼうろく

北海道の人。適当にもっさりまったり書きます。さくら荘のましろが好きです。アニメとかパソコンとか

Raspberry Pi ZeroにWatchdogを設定する

はじめに

自宅の室温監視用のラズパイが、気がつくと死んでいることが何度かありました。
原因を調べてもよくわからず、とりあえず電源OFF→ONで復帰するので、
それならwatchdogでもつければいいのでは?と思い立ったので、作業のメモ。

Watchdog基板つけなくてもSoCにWatchdog付いてるじゃん

外部watchdogを考えていましたが、RaspberryPi ZeroのSoC(BCM2835)には内蔵watchdog付いてるんですね。
しかし公式なドキュメントが全く出てこない・・・。

$ ls -al /dev/watchdog*
crw------- 1 root root  10, 130  5月 28 12:17 /dev/watchdog
crw------- 1 root root 250,   0  5月 28 12:17 /dev/watchdog0

/devにwatchdogという名前のなにかがいるのでwatchdogと呼ばれているなにかがありそうだけど。。

公式ドキュメントでwatchdogについて言及されているところとか知っている人いたら教えてください。

とりあえず他の人に前ならえする

絶対良くないんだけど、やるだけやってみることにする

参考にしたのは以下のサイト様たち
RaspberryPi 1BでBCM2835内蔵のウォッチドッグを有効にした - 悪霊にさいなまれる世界 -The Demon-Haunted World
Raspberry PiにハードウェアWatchdogを設定してみましょう | CANDY LINE Blog

手順

1. watchdogを有効化

/boot/config.txtを開いて以下の1行を追加する

dtparam=watchdog=on
2. watchdogのタイムアウト時間を設定

/etc/modprobe.d/bcm2835-wdt.confを開く(ない場合は新規で作成)
以下の1行を追加する。
これでどうやら15秒間ハートビートを待つ(15秒間に1度もハートビートが無いとwatchdogが発火する)らしい。

options bcm2835_wdt heartbeat=15 nowayout=0
3. ハートビート送信間隔の設定

/etc/systemd/system.confを開いて以下を探してコメントアウトを解除し、設定値を変更する。
これでsystemdがハートビートを5秒以内に1度送信してくれるらしい。
正常時はwatchdogのタイムアウトまで2回は通知されるようにしたいので、
ワーストケースを考えて5秒にした。

//↓これを探す
#RuntimeWatchdogSec=0
//↓これを追加
RuntimeWatchdogSec=5
4. 再起動で設定が反映される

再起動後、fork爆弾を使ってwatchdogのテストをしてみる。

$ :(){ :|:& };:

実行後すぐterminalの応答が返ってこなくなり、数分後に自動的に再起動することが確認できる。

最後に

公式のwatchdogについて書かれているドキュメントをご存じの方、教えてください。

Raspberry Pi Zero向けのデバイスドライバを作る(本編)

デバイスドライバ作成

はじめに

この記事は前回の続きになります。
gari30.hatenablog.com

仕事が忙しく、あまり作業時間が取れない状況でしたが、
やっとこさ出来上がったので、ここにまとめておきます。

ターゲットデバイスRaspberry Pi Zero WHになります。

参考にさせていただいたサイト様

(前回同様)RaspberryPi2上でデバイスドライバを作成する記事です。
リンクのページから連載されています。
組み込みLinuxデバイスドライバの作り方 (1) - Qiita

(前回同様)RaspbianとRaspbian Kernelについて説明がある記事です。
大変参考になりました。
Raspberry Pi でドライバ開発したい(基礎編) | なたで日記


出来たもの

github.com

詰まったところメモ

1. 環境変数設定スクリプト

参考にさせていただいたサイト様に記載のこちらのサイト を参考に、
ロスコンパイル環境の環境変数設定シェルスクリプトを作成しました。
deviceDriver/env.sh at master · Kyokko-OB-Team/deviceDriver · GitHub

. ./env.sh

のように、ドット(.)コマンドを使うと、

source ./env.sh

と同じ効果があるとのことを初めて知りました。
ドットコマンドを使うと、現在のコマンド実行環境で実行してくれるようです。

2. printk

躓いた、というか寝ぼけて間違えただけですが、念の為書いておきます。

kernel moduleではprintfは使えないです。
代わりにprintkが使えます。

3. __aeabi_uldivmod 未定義エラー
ERROR: "__aeabi_uldivmod" [/home/xxxx/deviceDriver/hc-sr04/hc-sr04.ko] undefined!

kernel module内で浮動小数点を使用するとこのエラーが出るらしい。

ソースコードではこの箇所

distance = ((falling_timestamp - rising_timestamp) / 58) * 1000;

Cキャストをするようにした。

distance = ((unsigned int)(falling_timestamp - rising_timestamp) / 58) * 1000;
4. 認識されないコマンドラインオプション '-fstack-protector-strong'
arm-linux-gnueabihf-gcc: エラー: unrecognized command line option ‘-fstack-protector-strong’

raspberry pi toolchainのgccのバージョンが古いことで発生するらしい。
使用しているコンパイラのバージョンはこれ。

$ /mnt/4tb/raspberrypi_kernel/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin/arm-linux-gnueabihf-gcc -v
組み込み spec を使用しています。
COLLECT_GCC=/mnt/4tb/raspberrypi_kernel/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin/arm-linux-gnueabihf-gcc
COLLECT_LTO_WRAPPER=/mnt/4tb/raspberrypi_kernel/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin/../libexec/gcc/arm-linux-gnueabihf/4.8.3/lto-wrapper
ターゲット: arm-linux-gnueabihf
configure 設定: /home/zhehe01/work/bzr/pi-build/builds/arm-linux-gnueabihf-raspbian-linux/.build/src/gcc-linaro-4.8-2014.03/configure --build=x86_64-build_unknown-linux-gnu --host=x86_64-build_unknown-linux-gnu --target=arm-linux-gnueabihf --prefix=/home/zhehe01/work/bzr/pi-build/builds/arm-linux-gnueabihf-raspbian-linux/install --with-sysroot=/home/zhehe01/work/bzr/pi-build/builds/arm-linux-gnueabihf-raspbian-linux/install/arm-linux-gnueabihf/libc --enable-languages=c,c++,fortran --disable-multilib --enable-multiarch --with-arch=armv6 --with-tune=arm1176jz-s --with-fpu=vfp --with-float=hard --with-pkgversion='crosstool-NG linaro-1.13.1+bzr2650 - Linaro GCC 2014.03' --with-bugurl=https://bugs.launchpad.net/gcc-linaro --enable-__cxa_atexit --enable-libmudflap --enable-libgomp --enable-libssp --with-gmp=/home/zhehe01/work/bzr/pi-build/builds/arm-linux-gnueabihf-raspbian-linux/.build/arm-linux-gnueabihf/build/static --with-mpfr=/home/zhehe01/work/bzr/pi-build/builds/arm-linux-gnueabihf-raspbian-linux/.build/arm-linux-gnueabihf/build/static --with-mpc=/home/zhehe01/work/bzr/pi-build/builds/arm-linux-gnueabihf-raspbian-linux/.build/arm-linux-gnueabihf/build/static --with-isl=/home/zhehe01/work/bzr/pi-build/builds/arm-linux-gnueabihf-raspbian-linux/.build/arm-linux-gnueabihf/build/static --with-cloog=/home/zhehe01/work/bzr/pi-build/builds/arm-linux-gnueabihf-raspbian-linux/.build/arm-linux-gnueabihf/build/static --with-libelf=/home/zhehe01/work/bzr/pi-build/builds/arm-linux-gnueabihf-raspbian-linux/.build/arm-linux-gnueabihf/build/static --enable-threads=posix --disable-libstdcxx-pch --enable-linker-build-id --enable-plugin --enable-gold --with-local-prefix=/home/zhehe01/work/bzr/pi-build/builds/arm-linux-gnueabihf-raspbian-linux/install/arm-linux-gnueabihf/libc --enable-c99 --enable-long-long --with-float=hard
スレッドモデル: posix
gcc バージョン 4.8.3 20140303 (prerelease) (crosstool-NG linaro-1.13.1+bzr2650 - Linaro GCC 2014.03)

回避策をggるとgccをアップデートしろ、と言われるが
raspberry pi用のクロスコンパイラはこれが最新版なのでアップデートできない。
csdnのこのページを参考に、kernelのMakefileを変更してエラーがでなくなった。

動作確認

カーネルモジュールをロードして、
テストアプリを実行して、
カーネルモールをアンロードして、
dmesgでログを確認

$ sudo insmod ./hc-sr04/hc-sr04.ko
$ sudo ./test_hc-sr04/test_hc-sr04
./test_hc-sr04/test_hc-sr04 ver.0.1
exec measure distance.
exec get distance.
distance: 6310000mm
$ dmesg
(省略)
[  223.047133] hc_sr04: loading out-of-tree module taints kernel.
[  223.058371] hc_sr04 init. ver.0.1.
[  418.259926] hc_sr04 module exit.

特にエラーが出ないのでOK

デバイスドライバの動作環境をまとめる

デバイスドライバを動作させるカーネルセットも保存しておきたいので、
動作環境をまとめておきます。

// kernelディレクトリに移動
$ cd linux/

// 一時コピー先ディレクトリ作成
$ mkdir -p device_tree/overlays

// デバイスツリーファイル群をコピー
$ cp ./arch/arm/boot/dts/*.dtb ./device_tree/.
$ cp ./arch/arm/boot/dts/overlays/*.dtb* ./device_tree/overlays/.

// デバイスツリーファイル群をアーカイブにする
$ cd device_tree/
$ tar -cvzf ../dtb.tar.gz *

// モジュールファイルをビルド
$ sudo make ARCH=arm INSTALL_MOD_PATH=./modules/ modules_install

// モジュールファイル群をアーカイブにする
$ cd modules/
$ tar -cvjf ../modules.tar.bz2 *

Raspberry Piで展開

まとめた動作環境ファイルをRaspberry Pi上にコピーしてから以下の様に
ファイルを展開する。

// カーネルイメージのバックアップ
$ sudo cp /boot/kernel.img /boot/kernel-bk.img

// カーネルイメージを更新
$ sudo cp ./kernel.img /boot/.

// デバイスツリーファイルを展開
// 所有者を変更できません。とエラーが出るので、--no-same-ownerオプションをつける
$ sudo tar --no-same-owner -xzvf ./dtb.tar.gz -C /boot/

// モジュールファイルを展開
$ sudo tar -xvjf ./modules.tar.bz2 -C /

// 再起動して起動確認
$ sync
$ sudo shutdown -r now

起動確認

まとめたデバイスドライバの動作環境をRaspberry Pi上で展開して
作成したカーネルモジュールとテストアプリでの動作確認をした。
OKだったので完璧!
以上

デキる人は知っているgnuplotの使い方

gnuplotを初めて使ったのでそのログ

はじめに

最近、おすすめの記事とかに「ショートカットを使いこなして仕事爆速!」とか
「デキる人は使ってる仕事術!」みたいなのがあって
記事を呼んでみると

Windowsはショートカットキーを使って時短!

とか

Excel、wordでおすすめのショートカットはこちら!ctrl+v!!

みたいなのばっかりで面白いので
似たタイトルにしてみた。

仕事だとデータ解析とかその結果はExcelが好まれるので
仕事で使う機会はあまりなかったけど
使えるようになりたいので勉強してみた。

やりたいこと

温度と湿度のデータがあるので、それを1つのグラフにしたい。
データはここに無限に上がってくるこれを使う。

1. 入力データの整理

入力データの形式はJSONもどきで、文字列が含まれるため
このままプロットできない。

ワンライナーを書いた。

cat ./202105_Temp_Humidi_Sensor_Data.json | sed -e 's/: /,/g' | sed -e 's/ /,/g' | sed -e 's/[\{\|\}\|"\]//g' | awk -F "," ' $2 == "2021/05/28" { print $3","$5","$6 }' > temp.log

ワンライナーでやってること
sed -e 's/: /,/g'
keyとvalueの区切りが: になっているので,にする。

sed -e 's/ /,/g'
日時データの日付と時間の間のスペースを,にする。

sed -e 's/[\{\|\}\|"\]//g'
行の両端の{}を消す。

awk -F "," ' $2 == "2021/05/28" { print $3","$5","$6 }'
区切り記号を,として、
日付を指定して時刻、温度、湿度のデータだけ抽出する。

2. gnuplotでグラフ作成

gnuplotがなければインストールする。

$ sudo apt install gnuplot

gnuplotで設定して、グラフを作る。

$ gnuplot
gnuplot> set timefmt "%H:%M:%S"  // 入力データの時間のフォーマット設定
gnuplot> set datafile separator ","    // 入力データの区切り記号を設定
gnuplot> set title "2021/05/29"        // グラフのタイトル設定
gnuplot> set xdata time                    // x軸を時間に設定
gnuplot> set format x "%H"              // x軸のフォーマット設定
gnuplot> set xlabel "time"                // x軸のラベル設定
gnuplot> set yrange [0:40]                // y軸のレンジ設定(0~40)
gnuplot> set ylabel "temp"               // y軸のラベル設定
gnuplot> set ytics nomirror              // y軸のメモリを片側表示に設定
gnuplot> set y2label "humidi"         // y2軸のラベル設定
gnuplot> set y2range [0:100]          // y2軸のレンジ設定(0~100)
gnuplot> set y2tics nomirror          // y2軸のメモリを片側表示に設定
gnuplot> set my2tics 10                 // y2軸のメモリの数を設定(10)
gnuplot> set terminal png             // グラフの出力設定(png)
// 出力ファイルの名前設定
gnuplot> set output "20210528_plot.png"
// グラフの作成(グラフ1の指定、グラフ2の指定)
// グラフ1: 入力ファイル、使うデータ、出力グラフ、グラフのプロット方法
// グラフ2: 入力ファイル、使うデータ、出力グラフ、グラフのプロット方法
gnuplot> plot "./temp.log" using 1:2 axis x1y1 with line title "temp", "./temp.log" using 1:3 axis x1y2 with line title "humidi"

これで出力したファイルがこれ
f:id:gari30:20210530010416p:plain

3. シェル化

作った。
measurementData/graph-make.sh at master · Kyokko-OB-Team/measurementData · GitHub

使い方

./graph-make.sh <任意の日付>_Temp_Humidi_Sensor_Data.json YYYY/MM/DD

Raspberry Pi Zero向けのデバイスドライバを作る(環境構築)

ラズパイゼロのデバイスドライバ開発環境構築

はじめに

Raspberry Pi ZeroでHC-SR04を使いやすくするために、
デバイスドライバを作成するので、
やったことをメモしておく。
とりあえず環境構築まで<2021/08/16修正>
再度環境構築し直したときに、記載ミスに気付いたのでコマンドを修正しました。

HC-SR04について

akizukidenshi.com

超音波をつかった距離センサです。
超音波パルスを送出して、
反射した超音波を受信するまでの時間で距離を求める仕組みです。
2cm~400cmまで、0.3cmの分解能で距離を測定できる仕様となっています。
お勉強がてらこいつのデバイスドライバを作ります。

参考にさせていただいたサイト

RaspberryPi2上でデバイスドライバを作成する記事です。
組み込みLinuxデバイスドライバの作り方 (1) - Qiita

今回の私と同じく、RaspberryPiZeroのデバイスドライバを別のLinuxPCで作成する記事です。
Raspberry Pi でドライバ開発したい(基礎編) | なたで日記

RaspbianとRaspbian Kernelについて説明がある記事です。
Raspberry Pi でカーネルモジュールビルドの準備 - Qiita

開発環境作成

1. 必要なものを集める

必要なパッケージをインストール

$ sudo apt install git bc bison flex libssl-dev make


カーネルとクロスコンパイラをclone

$ git clone https://github.com/raspberrypi/linux
$ git clone https://github.com/raspberrypi/tools
2. ラズパイのカーネルバージョンのソースにする

カーネルモジュール(*.ko)を作成する場合、
ターゲット環境のカーネルバージョンを合わせておくと面倒が無い。

ターゲットのカーネルバージョンの確認する。
今回は 5.4.51+ だった。

$ uname -r
5.4.51+

同じバージョン番号のコミットをここから探す。
Commits · Hexxeh/rpi-firmware · GitHub
今回はこのコミットだった。

同じバージョン番号のコミットのハッシュ値をコピーする。
今回は、8382ece2b30be0beb87cac7f3b36824f194d01e9だった。

コミットのハッシュ値githubに投げるとカーネルのリビジョンが取得できる。
(仕組みはわからん)
今回は、ff68b68ffe3e8f1759fd17532b6fe8dc4e211a2dだった。

$ export FIRM_REV=8382ece2b30be0beb87cac7f3b36824f194d01e9
$ curl -L https://github.com/Hexxeh/rpi-firmware/raw/${FIRM_REV}/git_hash

これで、現在ターゲットのRaspbianのカーネルのリビジョンがわかったので、
先程クローンしたカーネルを、先程取得したカーネルのリビジョンでチェックアウトする。
今回はこんな感じでブランチを切った。

$ git checkout -b raspbian-5.4.51_kernel ff68b68ffe3e8f1759fd17532b6fe8dc4e211a2d
3. カーネルのビルド

ターゲットによってビルド時に設定する環境変数が異なる。
今回は、Raspberry Pi Zeroなので以下のようにビルドする。

$ export ARCH=arm
$ export KERNEL=kernel
$ make bcmrpi_defconfig
#
# configuration written to .config
#
$ export CROSS_COMPILE=/mnt/4tb/raspberrypi_kernel/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin/arm-linux-gnueabihf-
$ make zImage modules dtbs
4. カーネルの書き込み

事前にRaspbianをmicroSDに焼いておく。
microSDをPCに接続して、ビルドしたカーネルを更新する。
今回は以下のようにした。

// マウント先ディレクトリの作成
$ sudo mkdir raspbian-boot
$ sudo mkdir raspbian

// マウント
$ sudo mount /dev/sdc1 /mnt/raspbian-boot
$ sudo mount /dev/sdc2 /mnt/raspbian

// カーネルモジュールの書き込み
$ sudo make ARCH=arm INSTALL_MOD_PATH=/mnt/raspbian/ modules_install

// カーネルのバックアップ
$ sudo cp /mnt/raspbian-boot/kernel.img /mnt/raspbian-boot/kernel_bk.img

// カーネルのコピー
$ sudo cp ./arch/arm/boot/zImage /mnt/raspbian-boot/kernel.img

// デバイスツリーのコピー
$ sudo cp ./arch/arm/boot/dts/*.dtb /mnt/raspbian-boot/.
$ sudo cp ./arch/arm/boot/dts/overlays/*.dtb* /mnt/raspbian-boot/overlays/.

// マウント解除
$ sudo umount /mnt/raspbian*

// マウント先ディレクトリの削除
$ sudo rm -rf ./raspbian*
5. 起動確認

ラズパイにmicroSDを戻して、通常通り起動するか確認する。
今回はコードに変更を加えなかったので、起動したらOKということにする

続き

この後の作業も記録しました。
gari30.hatenablog.com

組み込みLinuxでスクリーンショットを撮影する

組み込みでスクリーンショット

はじめに

組み込みLinuxはデスクトップ環境が無いので
スクリーンショットを取るアプリも無い。
でも操作画面(LCD)はあるからドキュメントを作るのにスクリーンショットが欲しい!

という上司からの要望があった。
半日でスクリーンショット撮影するツールを作らされたのでここにメモっておく。

こちらのサイトを参考にさせていただきました。
非常に助かりました。ありがとうございます。
ラズパイでフレームバッファ(/dev/fb0)を使用して、直接ディスプレイ画像を入出力する - Qiita
Bitmapファイルフォーマット

結論

フレームバッファを読みだしたRawファイルに、
ビットマップのヘッダをつけたらビットマップファイルとして開けた。

作ったものはgithubに置いてあります。
github.com

内容

結論までの経緯とやったことを書いておく。

フレームバッファとは

Linuxフレームバッファにデータを書き込むとカーネルがいい感じに勝手に描画してくれる。
つまりディスプレイの情報はフレームバッファに入っている。
カーネルが書き込んだフレームバッファをどう処理しているのかはわからない(そのうち理解したい)。

手元のUbuntuマシンでは/dev/fb0があった。

フレームバッファの情報は以下のコマンドで確認できるらしい。

$ cat /sys/class/graphics/fb0/bits_per_pixel
32

$ cat /sys/class/graphics/fb0/virtual_size 
1920,1080
フレームバッファを取得して見てみる

以下のコマンドでフレームバッファを取得する。

$ cat /dev/fb0 > temp.raw

IrfanViewでrawファイルを開く。
ソフトを起動して、ドラッグ&ドロップでrawファイルをぶち込むとrawファイルを開く設定ができる。

今回は以下の設定でrawファイルを正常に表示できた。

  • Image width:1920
  • Image heigh:1080
  • BitsPerPixel:32 BPP[4 byte per pixel]
  • Options for 24 and 32 BPP:Color order BGR(32bit BGRA)
rawファイルは扱いにくい

rawファイルでスクリーンショットを撮影することはできた。
でもrawファイルは一般的な画像ビューアで表示できないし、ドキュメントに貼り付けることもできない。

...と参ってたら、チームのすごい人から
「昔、bitmapでスクリーンショット撮るツールあった気がする。ツール見つけられないけど確かbitmapヘッダをくっつけてただけだった気がする。」
と、神の導きを頂いたので、とりあえずbitmapヘッダを作ってみることにした。

bitmapヘッダを作る

bitmapのヘッダは、ファイルヘッダ(14byte)と情報ヘッダ(40byte)の54byteでできているらしい。
とりあえず空のバイナリファイルを作る。

dd if=/dev/zero of=bmp_head.bin bs=54 count=1

vscode拡張機能vscode-hexdump」でヘッダにデータを入れていく。

ファイルタイプ以外はすべてリトルエンディアンでデータを入れる。
以下は実際に入れたデータ

種類 オフセット サイズ メモ
ファイルタイプ 0 2byte 0x42、0x4D 固定値
ファイルサイズ(byte) 2 4byte 0x00 1F A4 36 ヘッダサイズ(54byte)+1920*1080
予約領域1 6 2byte 0x00 固定値
予約領域2 8 2byte 0x00 固定値
ヘッダサイズ 10 4byte 0x00 00 00 36 固定値
情報ヘッダサイズ 14 4byte 0x00 00 00 28 固定値
画像の横幅(ピクセル) 18 4byte 0x00 00 07 80 1920
画像の縦幅(ピクセル) 22 4byte 0x FF FF FB C8 -1080(縦幅の2の補数)
プレーン数 26 2byte 0x00 01 固定値(プレーンとは?)
1画素の色数 28 2byte 0x00 20 bits_per_pixelの値
圧縮形式 30 4byte 0x00 00 00 00 rawファイルなので0固定
画像サイズ 34 4byte 0x00 1F A4 00 1920*1080
水平解像度(ppm) 38 4byte 0x00 00 00 00 固定値
垂直解像度(ppm) 42 4byte 0x00 00 00 00 固定値
色数 46 4byte 0x00 00 00 00 固定値
重要色数 50 4byte 0x00 00 00 00 固定値
rawファイルにbitmapヘッダをくっつける

必死こいてポチポチ計算してヘッダファイル作ったらあとは、rawファイルにくっつけるだけ。

cat bmp_head.bin temp.raw > test.bmp
何故かできた

これで何故かbitmapファイルでスクリーンショットが作れてしまった。
f:id:gari30:20210505033140p:plain

まとめ

フレームバッファにbitmapヘッダをつけたら上手くいく理由がいまいち理解できていない。
けど何故かできてしまった。(絶対良くないやつ)
ggっても関連する話が全く出てこなかったので、理由をご存じの方いらっしゃったら教えて下さい。。


とりあえず動けばいいツールだし、ユーザ空間でデバイスファイルのReadしかしていないので
OSに影響も無いだろうから一旦これで良いことにしておく。
理由の理解は今後の課題。