2022/05/25

GoogleTestのインストール

  C言語のコードテストには、GoogleTestを使っています。

 コードの単体テストはCUnitなどいろいろありますが、GoogleTestを使う最たる理由はGMockの存在があります。
 GMockは簡単に言うと、モック関数を簡単に作れるというところになります。
 CUnitなどではモック関数を、引数のチェック・動作の切り替えを全部用意しなくてはなりませんが、GMockはとても簡単にこれらのことができます。

 今回からしばらくは、GoogleTestの使い方について書いていきます。

●今回の環境

 Windows10上のWSL2のubuntu22.04で作業していきます。


●インストール手順

手順1:GoogleTestのソースコードをダウンロード

 GoogleTestのソースをダウンロードします。
 ダウンロード方法は2通りあり、aptを使う方法と、githubからダウンロードする方法があります。

・aptでダウンロード

$ sudo apt install googletest

 → /usr/src の下に、googletestディレクトリができる

 aptでは、ソースコードがダウンロードされるだけで、ビルド・インストールを自分で行う必要があります。

・githubからダウンロード

$ wget https://github.com/google/googletest/archive/refs/tags/release-1.11.0.tar.gz

 →現在のディレクトリに release-1.11.0.zip ファイルができる

 githubからダウンロードした場合は、解凍する必要があります

$ tar xvzf release-1.11.0.tar.gz

 →現在のディレクトリに、googletestディレクトリができる


 使い分けとしては、システム全体で共用したい場合はaptで、プロジェクトごとにバージョンを分けたい・独立させたいときは、githubからダウンロードするようにするとよいでしょう。


手順2:ビルド

 cmakeでGoogleTestをビルドします。
 cmakeがインストールされていない場合は、cmakeをインストールします。

$ sudo apt install cmake

 解凍したGoogleTestのディレクトリに入る。
  aptを使った場合 → /usr/src/googletest ディレクトリ
  githubからダウンロードした場合 → 解凍した場所にある googletestディレクトリ

 ビルド・インストールする。

$ cmake .
$ make
$ sudo make install

 →/usr/local/install の下に gtest と gmock のディレクトリができる
 →/usr/local/lib の下に libgmock.a libgmock_main.a libgtest.a libgtest_main.a ができる

※cmake で -DCMAKE_INSTALL_PREFIX=(path) オプションを与えてインストール先を変えることも可能

以上でインストール完了です。

●インストールの確認

 適当なディレクトリで、以下のファイルを用意してください。

test.cpp
#include <gtest/gtest.h>

TEST(ABC, abc)
{
}

 ビルドします。

$ g++ test.cpp -o test -lgtest -lgtest_main -lpthread

 → test 実行ファイルができる

 実行します。

 このような表示になれば、インストールが正常にできています。


 GoogleTestはC++のテストフレームワークですが、Cもテストできます。
 主にC言語での使い方を記載していきます。



2021/04/11

WSL2で仮想I2C通信

  WSL2でLinuxカーネルのI2Cのテスト機能を使って仮想I2C通信をできるようにします。

●今回の環境

 Windows10


 ディストリビューション

 カーネルバージョン

 環境はこれまでと同じです。

 Linuxカーネルをビルドできる環境が必要です。下記を参考にしてビルド環境を用意してください。

●方法

 まず、I2C機能、I2Cスタブ機能を有効にしたカスタムカーネルをビルドします。
 カスタムカーネルができれば、あとはI2C通信のモジュールをロードし、仮想I2Cデバイスを作成すれば、I2Cデバイスが無くてもPC内でI2C通信ができるようになります。

●カスタムカーネルの設定手順

 menuconfigを立ち上げます。
$ make menuconfig

手順1:Device Drivers に入ります


手順2:I2C Support へ入る


手順3:I2C Support の3項目を有効にする


 I2C support を有効にすると、さらに項目が表示されるので、I2C device interface と I2C/SMBus Test Stub を有効にします。
 そのほか2つがデフォルトで有効ですが、そのままにします。

手順4:設定を保存して終了する

 ここまでできたら、忘れずに設定を保存して menuconfig を終了します。
 また、Linuxカーネル名を変えておくとよいでしょう

手順5:カーネルをビルドし、I2C通信のLKMをインストールする

$ make
$ sudo make modules_install
 → /lib/modules の下に、カーネル名でディレクトリができます。
  この下の kernel ディレクトリ以下にI2C通信に必要な*.ko ファイルが格納されます。

手順6:udevのルールにI2Cのアクセス権を設定する(必要な場合のみ)

 ※udevルールが無いと、I2Cデバイスのアクセス権が root:root になってしまいます。

 まず、i2cグループを作り、ユーザーをi2cグループへ追加します。
$ sudo groupadd i2c
$ sudo gpasswd -a %USER i2c

 /etc/udev/rules.d に 以下の内容のファイルを 99-i2c.rule のファイル名で保存します。
SUBSYSTEM=="i2c-dev", GROUP="i2c", MODE="0660"

手順7:カスタムカーネルでWSL2を起動する

 今作った vmlinux ファイルでWSL2を再起動します。
 やり方は、以下の手順6~を参考にしてください。

手順8:I2C通信を有効にしたカーネルで起動できていることを確認する

$ uname -a
Linux earth 5.4.72-microsoft-standard-WSL2-i2c #1 SMP Wed Apr 10 23:11:51 JST 2021 x86_64 x86_64 x86_64 GNU/Linux

 ここまでが、I2C通信を有効にしたカスタムカーネルの作り方でした。

手順9:udevを起動する(必要な場合のみ)

 ※udevルールが無いと、I2Cデバイスのアクセス権が root:root になってしまいます。
$ /etc/init.d/udev start


 ここまでが、I2C通信を有効にしたカスタムカーネルの作り方でした。

●仮想I2Cデバイスを作成する

以下のコマンドを実行します。
$ sudo modprobe i2c-dev
$ sudo modprobe i2c-stub chip_addr=(slave addr)
 ※slave addr にはI2Cスレーブデバイスのスレーブアドレスを指定します。
  スレーブアドレスは10個まで指定できます。
   chip_addr=0x20            0x20 のI2Cデバイスが接続
   chip_addr=0x20,0x55    0x20 0x55 の2つのI2Cデバイスが接続

 → /dev の下に、i2c-* の名前で仮想I2Cデバイスができます。


 あとは、ふつうにI2Cの通信ができます。
 I2C Tools も使えます。

 SMBus stub driver となっているのが、仮想I2Cデバイスです。


2021/04/04

WSL2で仮想GPIOのアクセス権設定

 WSL2では、systemdが動かないのでudevが動いていないため、sysfsのGPIOのアクセス権が設定がされません。

 このため、GPIO操作するために毎度rootになるか、いちいちchmod,chownしないといけません。
 これは結構面倒なので、何とかしたいと思います。

 その前に、sysfsのgpio周りを、RaspberryPIと同じアクセス権設定にしたいと思います。
 まず準備として、gpioグループを作り、ログインユーザーをgpioグループへ追加します。
$ sudo groupadd gpio
$ sudo gpasswd $USER gpio

 idコマンドでgpioグループに入ったことが確認できます。

 そして、udevを使う場合は、gpio用のルールファイルを作っておきます。
 以下のファイルを /etc/udev/rules.d に 99-gpio.rules の名前で保存します。
SUBSYSTEM=="gpio", GROUP="gpio", MODE="0660"
SUBSYSTEM=="gpio*", PROGRAM="/bin/sh -c '\
        chown -R root:gpio /sys/class/gpio && chmod -R 770 /sys/class/gpio;\
        chown -R root:gpio /sys/devices/virtual/gpio && chmod -R 770 /sys/devices/virtual/gpio;\
        chown -R root:gpio /sys$devpath && chmod -R 770 /sys$devpath\
'"


 さて、それでは、アクセス権の設定方法は以下のいずれかが考えられます。
  1. daemonizeとgenieをインストールし、systemdをPID=1で動かす
  2. udevだけ無理やり動かす
  3. スクリプトを作ってexport/unexportで毎回設定する
 1の方法は、インターネットで「WSL2 systemd」で検索するとやり方がでてきます 。
 でも、なんか、ゴチャゴチャ入れたくないなぁ、と思います。

 2の方法ですが、試してみると、PID=1でなくても、動作するようです。
$ /etc/init.d/udev start


 すると、「インタラクティブシェルから実行されてます。多分、意図したことではないので、60秒待ちます。Ctrl+Cで止めてね。」というような文言がでてきますが、かまわず60秒待ちます。その後、

となってudevの起動が成功します。
後は gpio-mockup を modprobe で起動すれば、RaspberryPI で操作するのと同じ感じにできます。

udev起動時に60秒止められるのが嫌なら、/etc/init.d/udev スクリプト内で sleep(60)しているところを消してしまえばいいです(自己責任でお願いします)
 /etc/init.d/udev スクリプトから一部抜粋


 3の方法は、export/unexportするときに、全てのアクセス権を1つずつ設定していくようなスクリプトを作ればよいのでしょうが、そこまでするなら、sysfsインターフェースのgpioは使うのをやめたほうが良いでしょう。新しいgpioインターフェースにしましょう。

 以上が私なりの方法でした。
 まさか、WSL2がsystemd動いていないとは、思いませんでした。
 dockerはどうなんでしょうね。WSLやめてdockerにしようか。。。

2021/04/03

WSL2で仮想GPIO

   WSL2でLinuxカーネルのgpio-mockup機能を使って仮想GPIOを使えるようにします。


●今回の環境

 Windows10


 ディストリビューション

 カーネルバージョン

 今回の環境はまっさらなWSLで説明しますが、前回の仮想CANのカスタムカーネルの環境でもできます。

 Linuxカーネルをビルドできる環境が必要です。下記を参考にしてビルド環境を用意してください。

●方法

 GPIO機能、GPIO-MOCKUP機能を有効にしたカスタムカーネルをビルドします。
 カスタムカーネルができれば、あとはgpio-mockupモジュールをロードし、仮想GPIOデバイスを作成すれば、GPIOが無いPC上でGPIO操作ができるようになります。

●カスタムカーネルの設定手順

 menuconfigを立ち上げます。
$ make menuconfig

手順1:Device Drivers に入ります



手順2:GPIO Support を有効にする

 Device Drivers 内へ入り(リターンで入る)、GPIO Support を有効にします。


手順3:GPIO Testing Driver を有効にする

 GPIO Support 内へ入り、GPIO Testing Driver を有効にする。


 GPIO Testing Drivber が仮想GPIOドライバです。
 また、GPIOのsysfsインターフェースが必要なら、「/sys/class/gpio/... (sysfs interface)」も選択します。
 利用できるGPIOの数は「Maximum nuber of GPIOs for fast path」で設定します。

手順4:設定を保存して終了する

ここまでできたら、忘れずに設定を保存して menuconfig を終了します。
また、Linuxカーネル名を変えておくとよいでしょう。


手順5:カーネルをビルドし、仮想GPIOのLKMをインストールする

$ make
$ sudo make modules_install
 → /lib/modules の下に、カーネル名でディレクトリができます。
  この下の kernel ディレクトリ以下に仮想GPIOの gpio-mockup.ko ファイルが格納されます。

手順6:カスタムカーネルでWSL2を起動する

 今作った vmlinux ファイルでWSL2を再起動します。
 やり方は、以下の手順6~を参考にしてください。

手順7:GPIOを有効にしたカーネルで起動できていることを確認する

$ uname -a
Linux earth 5.4.72-microsoft-standard-WSL2-gpio #1 SMP Wed Mar 2 20:32:16 JST 2021 x86_64 x86_64 x86_64 GNU/Linux

 ここまでが、GPIOを有効にしたカスタムカーネルの作り方でした。


●仮想GPIOデバイスを作成する

gpio-mockupドライバをmodprobeコマンドで読み込みます。

書式:
    # modprobe gpio-mockup gpio_mockup_ranges=[x,y](,…,[x,y]) (gpio_mockup_named_lines)

    x:lineの開始番号、または、-1
    y:lineの終了番号、または xが-1のときlineの個数
 ※x = -1 のとき、割り当ては後ろからになるので注意

例1:gpio0~gpio7 の8つのGPIOを割り当てる
$ sudo modprobe gpio-mockup gpio_mockup_ranges=0,8
 → /dev の下に、gpiochip0 ができ、8line が使用できる
  また、sysfsインターフェースがある場合は、gpio0~7 までexportできる

例2:gpiochip0に4つ、gpiochip1に8つのGPIOを割り当てる
$ sudo modprobe gpio-mockup gpio_mockup_ranges=0,4,8,16
 → /dev の下に、gpiochip0 と gpiochip1 ができ、それぞれ 4line と 8line が使用できる
  また、sysfsインターフェースでは、gpio0~3 と gpio8~15 までexportできる

例3:gpiochip0とgpiochip1に後ろから8つずつGPIOを割り当てる
$ sudo modprobe gpio-mockup gpio_mockup_ranges=-1,8,-1,8
 → /dev の下に、gpiochip0 と gpiochip1 ができ、それぞれ 8line が使用できる
  また、sysfsインターフェースでは、gpio504~511 と gpio496~503 までexportできる

例4:各ラインにラベルを付ける
$ sudo modprobe gpio-mockup gpio_mockup_ranges=0,8,8,16 gpio_mockup_named_lines
 → 各ラインにラベル名が gpio-mockup-A-*、gpio-mockup-B-* という名前が付く
  また、sysfsインターフェースでは、gpio-mockup-A-*、gpio-mockup-B-*というような名前でexportされる


以上で仮想GPIOが使えるようになります。
libgpiod tools も普通に使えます。


●sysfsインターフェースのアクセス権について

 WSLでは、initプロセスが動いていないので、udevが動作していないため、/sys/class/gpio 以下のアクセス権がすべて root:root になってしまいます。
これらの対処法は次回まとめます。

2021/03/27

WSL2で仮想CAN通信

  WSL2でLinuxカーネルのSocketCAN機能を使って仮想CAN通信をできるようにします。

●今回の環境

 Windows10


 ディストリビューション

 カーネルバージョン

 環境は前回と同じです。

 Linuxカーネルをビルドできる環境が必要です。下記を参考にしてビルド環境を用意してください。

●方法

 まず、SocketCAN機能、VCAN機能を有効にしたカスタムカーネルをビルドします。
 カスタムカーネルができれば、あとはCAN通信のモジュールをロードし、仮想CANデバイスを作成すれば、CANデバイスが無くてもPC内でCAN通信ができるようになります。

●カスタムカーネルの設定手順

 menuconfigを立ち上げます。
$ make menuconfig

手順1:Networking suportを有効にする


 ※選択は、項目まで↑↓キーで移動して、スペースキーを押すとできます。

手順2:CAN bus subsystem support を有効にする

 Networking support 内へ入り(リターンで入る)、CAN bus subsystem support を有効にします。


手順3:CAN通信機能の3項目を有効にする

 CAN bus subsystem support 内へ入り、さらに下記の3項目を有効にする。


 デフォルトで既にこれらの項目が有効になっていると思いますが、念のために確認しておきます。

手順4:CAN Device Drivers 内の項目を有効にする

 CAN Device Driversの中へ入り、さらに以下の設定を行います。

 Virtual Local CAN Interface (vcan) が仮想CANドライバです。
 そのほか2つがデフォルトで有効ですが、そのままにします。

手順5:設定を保存して終了する

 ここまでできたら、忘れずに設定を保存して menuconfig を終了します。
 また、Linuxカーネル名を変えておくとよいでしょう。

手順6:カーネルをビルドし、CAN通信のLKMをインストールする

$ make
$ sudo make modules_install
 → /lib/modules の下に、カーネル名でディレクトリができます。
  この下の kernel ディレクトリ以下にCAN通信に必要な*.ko ファイルが格納されます。

手順7:カスタムカーネルでWSL2を起動する

 今作った vmlinux ファイルでWSL2を再起動します。
 やり方は、以下の手順6~を参考にしてください。

手順8:CAN通信を有効にしたカーネルで起動できていることを確認する

$ uname -a
Linux earth 5.4.72-microsoft-standard-WSL2-can #1 SMP Wed Mar 27 14:20:57 JST 2021 x86_64 x86_64 x86_64 GNU/Linux

 ここまでが、CAN通信を有効にしたカスタムカーネルの作り方でした。


●仮想CAN通信デバイスを作成する

以下のコマンドを実行します。
$ sudo modprobe can
$ sudo modprobe can_raw
$ sudo modprobe vcan
$ sudo ip link add dev vcan0 type vcan
$ sudo ip link set up vcan0
 → /sys/class/net の下に、vcan0 の名前で仮想CANデバイスができます。


あとは、ふつうに仮想CAN通信をすることができます。
can-utilsも普通に使えます。

※仮想CANデバイスは、PCを再起動すると消えてしまうので、また modprobe からコマンド実行してください。

  


2021/03/21

WSL2のカーネルをカスタムビルドする

 WSL2のLinuxカーネルを、コンフィグ設定してビルド・使用します。

●今回の環境

 Windows10


 ディストリビューション

 カーネルバージョン

 環境は前回と同じです。
 前回のビルド環境をそのまま使う場合は、下記の手順3から始めてください。

●ビルド手順

手順1:カーネルソースをダウンロード

 カーネルのソースコードはGitHubのMicrosoft公式のものを使用します。
$ curl -OL https://github.com/microsoft/WSL2-Linux-Kernel/archive/linux-msft-5.4.72.tar.gz
 → linux-msft-5.4.72.tar.gz ファイルができる

 解凍します。
$ tar -zxf linux-msft-5.4.72.tar.gz
 → WSL2-Linux-Kernel-linux-msft-5.4.72 ディレクトリができる

手順2:カーネルのビルドに必要なソフトをインストール

 カーネルソース内の README-Microsoft.WSL2 ファイルの2に書かれているツールをインストールします。
 ただし、これaptでインストールするのに、sudo とinstall の間に apt が抜けているので、aptを追記して実行すること(昔から抜けてる。いいかげん直しなよ。。。)
$ sudo apt install build-essential flex bison libssl-dev libelf-dev

手順3:.configファイルの準備

 ソースのディレクトリへ入り、Microsoft/config-wsl ファイルをコピーする
$ cd WSL2-Linux-Kernel-linux-msft-5.4.72
$ cp Microsoft/config-wsl .config


手順4:.configファイルを編集

 方法はいくつかありますが、今回はmenuconfigを使ってGUIで編集します。

 事前にmenuconfig用ツールのインストールが必要
$ sudo apt install libncurses-dev

 上記のツールが入っていれば、以下のコマンドを実行
$ make menuconfig


 ここで、カーネルに組込む、またはカーネルモジュールの有効・無効を切り替えます。

 今回は、カーネルの名前を変えることとします。
 以下の項目を設定します。

 General setup   --->
   Local version - append to kernel release

 WSL2のデフォルトは「-microsoft-standard-WSL2」なので、この名前を適当に変えます。
 今回は例として「-microsoft-standard-WSL2-custom」とします。
 この設定は、「uname -r」 で表示される名前が変ります。

 書き換え出来たら、セーブして終了します。

手順5:カーネルをビルド

$ make
 → vmlinuxファイルができる

 前回はコンフィグファイルを指定しましたが、今回は先ほど作った .config を使います。

手順6:vmlinuxファイルをWindows側へコピー

 コピー先は任意の場所を使用できるが、Windowsの領域へコピーする。
 今回の例では、Windowsの%USERPROFILE%ディレクトリの下にwsl2/5.4.72-microsoft-standard-WSL2-customディレクトリを作って、そこへコピーすることにします。
 ※(Windowsのユーザー名)は各環境で変えること
 ※%USERPROFILE%ディレクトリ = c:\Users\(Windowsのユーザー名) のディレクトリ
$ mkdir -p /mnt/c/Users/(Windowsのユーザー名)/wsl2/5.4.72-microsoft-standard-WSL2-custom
$ cp vmlinux /mnt/c/Users/(Windowsのユーザー名)/wsl2/5.4.72-microsoft-standard-WSL2-custom

手順7:.wslconfigファイルで使用するlinuxカーネルを指定する

 %USERPROFILE%ディレクトリの直下に .wslconfigファイルを、以下の内容で作る。
 ※"\"はエスケープシーケンスになるので2つ続けること
[wsl2]
kernel = c:\\Users\\(Windowsのユーザー名)\\wsl2\\5.4.72-microsoft-standard-WSL2-custom\\vmlinux

手順8:wslをシャットダウンする

 PowerShellで、wslをシャットダウンする。
PS > wsl --shutdown

手順9:wslを起動し、unameでバージョンを確認する

$ uname -a
Linux earth 5.4.72-microsoft-standard-WSL2-custom #1 SMP Wed Mar 21 15:20:57 JST 2021 x86_64 x86_64 x86_64 GNU/Linux

 ビルド日時などから、今作ったカーネルで起動できていることを確認する。


以上で、カーネルのカスタムビルドから起動するまででした。

次回は、Linuxカーネルの仮想CAN通信を有効にします。

2021/03/18

WSL2のカーネルを自分でビルドして使う

 WSL2でubuntuをインストールすると、/lib/modules以下がカラなので、PC上で仮想CAN、仮想GPIOなどのドライバが使えなかったり、ローダブルカーネルモジュール(LKM)のビルドができません。
 このため、Linuxカーネルのコンフィグからこれらのオプションを有効にしたり、LKMをビルドできる環境を用意する必要があります。
 まずは、WSL2のLinuxカーネルを自分でビルドして使えることを確認します。

●今回の環境

 Windows10


 ディストリビューション

 カーネルバージョン

今回はカーネルのコンフィグはWSL2デフォルトのままビルドします。
今のカーネルバージョンが5.4.72なので、同じ5.4.72を使います。
仮想CANを有効にしたカスタムビルドは次回行います。

●ビルド手順

手順1:カーネルソースをダウンロード

 カーネルのソースコードはGitHubのMicrosoft公式のものを使用します。
$ curl -OL https://github.com/microsoft/WSL2-Linux-Kernel/archive/linux-msft-5.4.72.tar.gz
 → linux-msft-5.4.72.tar.gz ファイルができる

 解凍します。
$ tar -zxf linux-msft-5.4.72.tar.gz
 → WSL2-Linux-Kernel-linux-msft-5.4.72 ディレクトリができる

手順2:カーネルのビルドに必要なソフトをインストール

 カーネルソース内の README-Microsoft.WSL2 ファイルの2に書かれているツールをインストールします。
ただし、これaptでインストールするのに、sudo とinstall の間に apt が抜けているので、aptを追記して実行すること(昔から抜けてる。いいかげん直しなよ。。。)
$ sudo apt install build-essential flex bison libssl-dev libelf-dev

手順3:カーネルをビルド

$ cd WSL2-Linux-Kernel-linux-msft-5.4.72
$ make KCONFIG_CONFIG=Microsoft/config-wsl
 → vmlinuxファイルができる

手順4:vmlinuxファイルをWindows側へコピー

 コピー先は任意の場所を使用できるが、Windowsの領域へコピーする。
 今回の例では、Windowsの%USERPROFILE%ディレクトリの下にwsl2/5.4.72-microsoft-standard-WSL2ディレクトリを作って、そこへコピーすることにします。
 ※(Windowsのユーザー名)は各環境で変えること
 ※%USERPROFILE%ディレクトリ = c:\Users\(Windowsのユーザー名) のディレクトリ
$ mkdir -p /mnt/c/Users/(Windowsのユーザー名)/wsl2/5.4.72-microsoft-standard-WSL2
$ cp vmlinux /mnt/c/Users/(Windowsのユーザー名)/wsl2/5.4.72-microsoft-standard-WSL2

手順5:.wslconfigファイルで使用するlinuxカーネルを指定する

 %USERPROFILE%ディレクトリの直下に .wslconfigファイルを、以下の内容で作る。
 ※"\"はエスケープシーケンスになるので2つ続けること
[wsl2]
kernel = c:\\Users\\(Windowsのユーザー名)\\wsl2\\5.4.72-microsoft-standard-WSL2\\vmlinux

手順6:wslをシャットダウンする

 PowerShellで、wslをシャットダウンする。
PS > wsl --shutdown

手順7:wslを起動し、unameでバージョンを確認する

$ uname -a
Linux earth 5.4.72-microsoft-standard-WSL2 #1 SMP Wed Mar 17 20:42:16 JST 2021 x86_64 x86_64 x86_64 GNU/Linux

ビルド日時などから、今作ったカーネルで起動できていることを確認する。

以上で終わりです。

次回は、カーネルのカスタムビルドを行います。

2019/05/06

仮想GPIOドライバひとまず完成

仮想GPIOドライバがひとまず完成しました。

GitHubの以下の場所にアップしています。
vgpio

動作確認は、ubuntu 18.04 (2019/05/06時点のupdate済)でしました。

●使い方
0.ビルドする。
cd vgpio
make
→vgpio.koができる。

1.ドライバをinsmodする。
sudo insmod vgpio.ko
→/sys/kernel/vgpioができる。

2.使いたいGPIOポートを作成する。
(例)GPIO23を作成する場合
echo 23 > /sys/kernel/vgpio/export
→GPIO23ができる。

あとは、通常のGPIOと同じ使い方です。

3.direction、value、edge、active_lowを読み書きする。
(例)GPIO23を入力モードにして、1回読み出す
echo "in" > /sys/kernel/vgpio/gpio23/direction
cat /sys/kernel/vgpio/gpio23/value


4.もう使わない場合は、vgpio.koをrmmodする
sudo rmmod vgpio.ko
→/sys/kernel下のvgpioと、作っていたgpio23が消える。
※作成したGPIOポートは、unexport操作しなくても
rmmodすると自動的にunexportします。

使い方は以上です。

●注意点
※1.本物のGPIOは、入力モードに設定しているので
書き込みはできませんが、この仮想GPIOでは、
入力モードでも書き込みができてしまいます。
デバッグで、GPIOの値を変える場合は、別コンソールで
echoなどでvalueの値を書き換えてください。

※2.select/pollのシステムコールが、現在使えません。
すぐできそうですですが、私がやりたいことは
現状でできたので、とりあえずこのままにします。

----------------------------------------------------------

この仮想GPIOドライバは、linuxカーネルのリポジトリの
以下のファイルを参考にしました。

v4.18
/samples/kobject/kobject-example.c
/drivers/gpio/gpiolib-sysfs.c

・アクセス権について
本物のGPIOでは、otherのアクセス権が読出しのみになっています。
これはカーネルレベルで制限かけられていました。
/include/linux/kernel.h VERIFY_OCTAL_PERMISSIONSマクロ
/fs/sysfs/group.c create_files関数

ラズベリーパイなどでは、GPIO操作用のGPIOグループがあり、
piユーザはGPIOグループに属しており、
GPIOの各属性にGPIOグループで読み書きしているようです。

PCでデバッグするとき、GPIOグループ作って、各属性を
GPIOグループに変えて、GPIOグループへの権限を変えて・・・
ということをいちいちするのは面倒なので、
仮想GPIOドライバではちょっと細工して、otherでも読み書き
できるようにしました。

・select/poll対応について
現在はできません。なので、edgeになにか書き込んでも
特に影響ありません。(書き込み自体はできます)
カーネルの以下の機能を使えばできそうであることはわかっています。
sysfs_get_dirent()  edgeがnone以外が書き込まれたら、kernfs_nodeを取得する
sysfs_notify_dirent() valueが変化したら、kernfs_nodeにイベント発生を通知する
sysfs_put() edge、active_lowが変化したら、kernfs_nodeを解放する

2019/05/03

仮想GPIOを/sys/class以下に作ってみたものの

仮想GPIOドライバをとりあえず作ってみました。
設定値をメモリ上にとって、gpiochipへの読み書きを
メモリの値に置き換えるだけなので、とりあえず動くものは
すぐにできました。

gpiolib-sysfs.cを参考にして、/sys/class以下に
vgpioという名前のディレクトリを作って、
その配下にexport等を作るようにしました。
しかし、同時に/sys/devices/virtual/vgpioもできてしまう。

また、動作させてexportへ任意の番号を書き込むとgpioNができるが、
このエイリアスが/sys/devices/virtual/vgpio/gpioNになり、
direction、value、edge、active_low以外にpower、uevent等
よくわからないものがたくさんできていました。

ただ direction/value/edge/active_low だけ読み書きできれば
良いのですが・・他のはsysfsのclassAPIが作ってるものなので、
なにか意図しないことが起きるかもしれない・・

というわけで、別の方法を模索してみます。

linux kernelのリポジトリを見ると、/samples/kobject があります。
これは、/sys/kernel以下にアトリビュートというファイルのようなものを
作って読み書きするサンプルです。
(sysfsはファイルではなくアトリビュートと言う)
これが、ほぼやりたいことをしているので、これを参考にして作り直します。
こんなのがあったんだな。

2019/04/30

GPIO操作のPCテスト環境について考える

GPIO操作のテスト環境は、ほぼ実機でのテストしかできません。
(と思ってるけど、実はあるのだろうか・・)

例えば、GPIO23に出力するとき、コンソールで以下のようにします。
> echo 23 > /sys/class/gpio/export
> echo "out" > /sys/class/gpio/gpio23/direction
> echo 1 > /sys/class/gpio/gpio23/value

この操作を、実機ではなくPC上で行おうとすると、
最初の exportへ23を書き込む ところで、引数エラーになります。
> echo 23 > /sys/class/gpio/export
echo: 書き込みエラー: 無効な引数です

dmesgで見ると、以下のメッセージが出力されています。
> dmesg
    ~省略~
[  955.6530668] export_store: invalid GPIO 23

これを出してるのは、linux v4.18.0 のカーネルソースを見ると、
以下の場所で出力しているようです。
/drivers/gpio/gpiolib-sysfs.c
453 static ssize_t export_store(struct class *class,
454    struct class_attribute *attr,
455    const char *buf, size_t len)
456 {
    ~省略~
465     desc = gpio_to_desc(gpio);
466     /* reject invalid GPIOs */
467     if (!desc) {
468         pr_warn("%s: invalid GPIO %ld\n", __func__, gpio);
469         return -EINVAL;
470     }

エラーになってるのはgpio_to_desc関数で、引数gpioに対応する
ディスクリプタを返すが、このディスクリプタはカーネルソース内で
チップ情報によって作成されています。つまり、この動作を変えるには、
カーネルソースの再ビルドが必要となりそうです。

なので、GPIO操作のテストには実機が必要だが、現場では数人で
実機を共有しているので、気軽にテストできません。
ようやく実機使えてテストしたら、「不等号の向き間違えた」なんかの
つまらないバグが出ると、時間の無駄だし、気が滅入ってきます。
なので、PC上でコーディングミスレベルの不具合は潰しておきたい。

PC上での方法を考えると、デバッグ時は/sys/class/gpio/gpioN/value を
mkfifo で作ったパイプで代替する方法をしている人が、私の職場でいました。
しかし、これは、書き込み側は動作は変わらないが、読み出し側がパイプを
readしたときに書き込みがあるまで関数がブロックしてしまいます。
また、読み出しが遅い場合、書き込みを、例えば2回1を書き込んだ場合、
読み出すと11になるので、一文字ずつ処理するなど、実機の場合には
不要な処理を足さなくてはならないので面倒です。

カーネルソースのgpiolib-sysfs.cを見ていると、実ポートには出力せず、
メモリ上の値を読み書きするだけの、GPIOの動作を模擬できる
仮想GPIOドライバが作れそうです。

というわけで、このゴールデンウィーク中に仮想GPIOドライバを
作ってみることにしました。

2017/12/01

Adafruit Trinket M0

ネットのおすすめ記事に、面白そうなものがあった。

Trinket M0ではじめるCircuitPython

詳細は、上の記事に譲りますが、
要は、CircuitPythonってやつを使うと、
PCに刺すとただのUSBメモリに見えるが、
main.pyにpythonプログラムを書いておくと、
マイコン起動時にmain.pyが実行されるもよう。

これは便利そう。

スイッチ入力で、150ms以上のメーク信号でON、それ以下の時間
(例えば100ms)では無視する、なんてのをテストするとき、
人間の手では正確な時間の入力ができないのでPICを使っています。

が、PICでは、プログラム書いて、ビルドして、PICライタで
書き込んで、ブレッドボードに組み込んで・・ってやってたのが、
CircuitPythonではビルド→PICライタの作業がいらなくなります。

PICライタを複数人で使いまわしているので、PICライタで
書こうとしたときに無い、誰かがどこかへもってっちゃったなど
書き換えできない時が多々あります。
また、出張先のPCにIDEが無い、インストールには管理部門に
申請・許可がいる、インターネットにつながせてもらえない
など、今すぐ使いたいのに使えないということも多々あります。

Trinket M0欲しい。
スイッチサイエンスでしか売ってない。
Amazonでも買えるようにならないかなー。

※2017/12/19追記
CircuitPythonは、MicroPythonがもとになっているそうな。
MicroPythonが、PCに刺すとUSBメモリのようになって、
PCに刺さずに電源入れるとmain.pyに書いたプログラムを動かす、
というものの元祖のようです。欲しい。

2017/10/22

OpenOCDのインストール for ubuntu

OpenOCDのインストール方法についてまとめます。

環境:ubuntu 17.10

インストールするコマンド
$ sudo apt install openocd
これで、/usr/bin 配下に openocd がインストールされます。

念のため、バージョンを確認すると、
$ openocd -v
Open On-Chip Debugger 0.10.0
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html

現時点での最新バージョンがインストールされていました。

なお、アンインストールする場合は、以下のようにします。
$ sudo apt remove openocd

2017/10/21

ARM用コンパイラのインストール for ubuntu

ARM用コンパイラ arm-none-eabi-gcc のインストール方法をまとめます。

環境:ubuntu 17.10

1.aptのリポジトリを追加
$ sudo add-apt-repository ppa:team-gcc-arm-embedded/ppa

2.パッケージリストを更新
$ sudo apt-get update

3.ARM用gccのダウンロード・インストール
$ sudo apt-get install gcc-arm-embedded

これで、/usr/bin 配下に、arm-none-eabi-gcc がインストールされます。
$ which arm-none-eabi-gcc
/usr/bin/arm-none-eabi-gcc

念のため、バージョンを確認すると、
$ arm-none-eabi-gcc -v
    ~省略~
gcc version 7.2.1 20170904 (release) [ARM/embedded-7-branch revision 255204] (GNU Tools for Arm Embedded Processors 7-2017-q4-major)
現時点での最新バージョンがインストールされていました。

なお、アンインストールする場合は、以下のようにします。
$ sudo apt-get remove gcc-arm-embedded


参考文献
https://launchpad.net/~team-gcc-arm-embedded/+archive/ubuntu/ppa

2017/09/17

Raspberry PiでCAN通信 その6【Pythonで標準フレームの受信】

前回のC言語で書いたCANの標準フレームの受信を
Pythonで実装します。

動作環境も前回と同じく、OS:raspbian jessie
仮想CANデバイス(vcan1)でテストしています。

#! /usr/bin/env python3
# -*- coding: utf-8 -*-

"""
    sudo modprobe vcan
    sudo ip link add dev vcan1 type vcan
    sudo ip link set vcan1 up
"""

import sys
import socket
import struct

CAN_MAX_DLEN = 8
CAN_MTU = 16

CAN_NAME = 'vcan1'

def canrecv_stdframe():

    s = socket.socket(socket.AF_CAN, socket.SOCK_RAW, socket.CAN_RAW)

    try:
        s.bind((CAN_NAME,))
    except OSError:
        print('bind error', file=sys.stderr)
        return -1

    frame = s.recv(CAN_MTU)

    id, dlc, bdata = struct.unpack('IB3x8s', frame)
    data = bdata.hex()

    s.close()

    return id, dlc, data

if __name__ == '__main__':
    def main():
        id, dlc, data = canrecv_stdframe()

        print('id=%x, dlc=%s, data=%s' % (id, dlc, data))

    main()

これで、標準フレームを1回受信し、表示して終わります。

Raspberry PiでCAN通信 その5【C言語で標準フレームの受信】

Raspberry Pi上で、C言語によるCAN通信の標準フレーム受信をします。

動作環境は、OS:raspbian jessie です。
CANデバイスは、まだCANモジュールが来ていないので、
以下のコマンドで作った仮想CANデバイス(vcan1)で受信します。
sudo modprobe vcan
sudo ip link add dev vcan1 type vcan
sudo ip link set vcan1 up

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <linux/can.h>
#include <linux/can/raw.h>


#define CAN_NAME "vcan1"


int canrecv_stdframe(unsigned short *p_id, unsigned char *p_dlc, unsigned char data[])
{
    int ret;
    int s;
    fd_set rdfs;
    struct ifreq ifr;
    struct sockaddr_can addr;
    struct timeval timeout;
    struct can_frame frame;
    int nbytes;

    if((s = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0)
    {
        perror("socket");
        return -1;
    }

    memset(&ifr.ifr_name, 0, sizeof(ifr.ifr_name));
    strncpy(ifr.ifr_name, CAN_NAME, sizeof(ifr.ifr_name));

    ifr.ifr_ifindex = if_nametoindex(ifr.ifr_name);
    if(! ifr.ifr_ifindex)
    {
        perror("if_nametoindex");
        return -2;
    }

    addr.can_family = AF_CAN;
    addr.can_ifindex = ifr.ifr_ifindex;

    if(bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0)
    {
        perror("bind");
        return -3;
    }

    while(1)
    {
        FD_ZERO(&rdfs);
        FD_SET(s, &rdfs);

        timeout.tv_sec = 1;
        timeout.tv_usec = 0;

        ret = select(s+1, &rdfs, NULL, NULL, &timeout);
        if(ret < 0)
        {
            perror("select");
            return -4;
        }
        else if(0 == ret)
        {
            continue;
        }
        else
        {
            break;
        }
    }

    nbytes = read(s, &frame, sizeof(frame));
    if(nbytes < 0)
    {
        perror("recv");
        return -5;
    }

    if(nbytes == sizeof(frame))
    {
        *p_id = frame.can_id;
        *p_dlc = frame.can_dlc;
        memcpy(data, frame.data, CAN_MAX_DLEN);
    }
    else
    {
        fprintf(stderr, "recv size not std-frame.\n");
    }

    close(s);

    return 0;
}

int main(int argc, char argv[])
{
    unsigned short id;
    unsigned char dlc;
    unsigned char data[CAN_MAX_DLEN];

    canrecv_stdframe(&id, &dlc, data);

    printf("id=%x, dlc=%d, data=%.02x %.02x %.02x %.02x %.02x %.02x %.02x %.02x\n",
        id, dlc, data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]);
}


これで、標準フレームを1回受信し、表示して終わります。

CAN標準フレームの受信に必要な処理は、これでわかりました。

次は、これをpythonで実装します。

2017/09/16

Raspberry PiでCAN通信 その4【Pythonで標準フレームの送信】

前回のC言語で書いたCANの標準フレームの送信を
Pythonで実装します。

PythonでCAN通信するには、python-canを使えばよいようですが、
私の職場はインターネットに接続させてもらえないので、
pipでpython-canをインストールすることができません。

pythonでsocketが使えるので、SocketCANを使って、
CAN通信するプログラムを作ることにしました。

動作環境も前回と同じく、OS:raspbian jessie
仮想CANデバイス(vcan1)でテストしています。

#! /usr/bin/env python3
# -*- coding: utf-8 -*-

"""
    sudo modprobe vcan
    sudo ip link add dev vcan1 type vcan
    sudo ip link set vcan1 up
"""

import sys
import socket
import struct

CAN_MAX_DLEN = 8
CAN_MTU = 16

CAN_NAME = 'vcan1'

def cansend_stdframe(id, data):

    if(CAN_MAX_DLEN < len(data)):
        print('dlc too long.', file=sys.stderr)
        return -1

    s = socket.socket(socket.AF_CAN, socket.SOCK_RAW, socket.CAN_RAW)

    s.setsockopt(socket.SOL_CAN_RAW, socket.CAN_RAW_FILTER, None, 0)

    # no loopback
    loopback = 0 # 0 = disabled, 1 = enabled(default)
    s.setsockopt(socket.SOL_CAN_RAW, socket.CAN_RAW_LOOPBACK, loopback)

    try:
        s.bind((CAN_NAME,))
    except OSError:
        print('bind error', file=sys.stderr)
        return -4

    dlc = len(data)
    bdata = bytes(data)
    frame = struct.pack('IB3x8s', id, dlc, bdata)

    if(s.send(frame) < CAN_MTU):
        print('send', file=sys.stderr)
        return -5

    s.close()

    return 0

if __name__ == '__main__':
    def main():
        data = [ 0x11, 0x22, 0x33, 0x44 ]
        cansend_stdframe(0x0123, data)

    main()

これで、id=0x123,data=0x11 0x22 0x33 0x44 のデータを1回送信します。



2017/09/11

Raspberry PiでCAN通信 その3【C言語で標準フレームの送信】

Raspberry Pi上で、C言語によるCAN通信の標準フレーム送信をします。

can-utilsのソースコードを解析した結果、
標準フレームの送信をする処理を起こしました。

動作環境は、OS:raspbian jessie です。
CANデバイスは、まだCANモジュールが来ていないので、
以下のコマンドで作った仮想CANデバイス(vcan1)で送信します。
sudo modprobe vcan
sudo ip link add dev vcan1 type vcan
sudo ip link set vcan1 up

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <linux/can.h>
#include <linux/can/raw.h>

#define CAN_NAME "vcan1"

int cansend_stdframe(unsigned short id, unsigned char dlc, unsigned char data[])
{
    int s;
    struct ifreq ifr;
    struct sockaddr_can addr;
    struct can_frame frame;
    int loopback;

    if(CAN_MAX_DLEN < dlc)
    {
        fprintf(stderr, "dlc too long.\n");
        return -1;
    }

    memset(&frame, 0, sizeof(frame));
    frame.can_id = id;
    frame.can_dlc = dlc;
    memcpy(&(frame.data[0]), &(data[0]), dlc); 

    if((s = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0)
    {
        perror("socket");
        return -2;
    }

    memset(&ifr.ifr_name, 0, sizeof(ifr.ifr_name));
    strncpy(ifr.ifr_name, CAN_NAME, sizeof(ifr.ifr_name));

    ifr.ifr_ifindex = if_nametoindex(ifr.ifr_name);
    if(! ifr.ifr_ifindex)
    {
        perror("if_nametoindex");
        return -3;
    }

    addr.can_family = AF_CAN;
    addr.can_ifindex = ifr.ifr_ifindex;

    setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);

    /* no loopback */
    loopback = 0; /* 0 = disabled, 1 = enabled(default) */
    setsockopt(s, SOL_CAN_RAW, CAN_RAW_LOOPBACK, &loopback, sizeof(loopback));

    if(bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0)
    {
        perror("bind");
        return -4;
    }

    if(write(s, &frame, CAN_MTU) < CAN_MTU)
    {
        perror("write");
        return -5;
    }

    close(s);

    return 0;
}

int main(int argc, char argv[])
{
    unsigned char data[] = { 0x11, 0x22, 0x33, 0x44 };

    cansend_stdframe(0x0123, sizeof(data), data);
}

ループバックをoffにしているので、candumpで見ていても、何も出てきません。
loopbackの値を1にすれば、出てくるようになります。

CAN標準フレームの送信に必要な処理は、これでわかりました。

次は、これをpythonで実装します。

2017/09/10

SocketCANによる仮想CAN通信

Raspberry PiでCAN通信しようと、CAN-Utilsの中身を調べています。

LinuxのSocketCANを利用して通信しているようでした。

SocketCAN

SocketCANとは何ぞやと、上記の文章を読んでいると、
以下の章で衝撃を受けました。

6.4 The virtual CAN driver (vcan)

なんと、仮想CANドライバという機能があるようです。

CANデバイスをリンクアップするときに、
実デバイスがなくても、仮想的なCAN通信ができるようです。
仮想デバイスを作る方法は以下の通り。
$ sudo modprobe vcan
$ sudo ip link add dev vcan0 type vcan
$ sudo ip link set vcan0 up

これで、/sys/class/net の下にvcan0ができ、
vcan0 という名前でデバイスが利用できるようになります。

以下は、CANデバイスが無いPC上で、CAN-Utilsを使用した例です。

送信側


受信側


こんなことができるなら、CANモジュール買わなくてもよかったか・・・
まあ、CANモジュールが届くまでは、仮想CANで開発ができそうだ。

Raspberry PiでCAN通信 その2【CAN-Utils】

CANモジュールがまだ来ないので、まだCAN通信ができませんが、
待っててもしょうがないので、CAN通信をどうプログラムするのか
調べます。

CAN通信でネットを検索すると、「CAN-Utils」というオープンソースの
パッケージが多数出てきます。まずはこのパッケージをインストールして、
公開されているソースを見ながら、調査を進めていきます。

まずはCAN-Utilsのインストールから。
ホームディレクトリ以下にダウンロードして、インストールすることにします。
$ cd
$ sudo apt-get install autoconf git libtool
$ git clone https://github.com/linux-can/can-utils.git
$ cd can-utils
$ ./autogen.sh
$ ./configure
$ make
$ sudo make install

これで、/usr/local/bin 配下に、CAN-Utilsの実行ファイルが入りました。

主に使うコマンドは、受信はcandumpコマンド、送信はcansendコマンドです。

以下、/sys/class/net/can0インターフェースがある前提で、記載します。

受信したすべてのCANフレームを表示するときは、
$ candump can0
で、表示できます。フィルターを設定することもできます。

送信する場合は、arb=123,data=0xAA 0xBB 0xCC 0xDD の場合、
$ cansend can0 123#AABBCCDD
とします。標準フレーム、拡張フレーム両方できるようです。

これから、cansendのソースを眺めたいと思います。

注文したCANモジュールは、まだ「発送しました」になってた。
進んでるんだろうか。
はやくCANモジュール来ないかなー。

2017/09/08

Raspberry PiでCAN通信 その1【設定編】

注文したCANモジュールが到着するまでに、
Raspberry Piでどう設定するのかを調べました。

以下は、Raspberry Pi 3 Model B 、OSはRaspbian Jessieでの設定です。

MCP2515は、マイコンとはSPIで接続するので、
まず、以下の操作で、Raspberry PiのSPI通信を有効にします。

Raspberry Piにログインして、raspi-configを起動します。
$ sudo raspi-config

「5 Interfacing Options」を選択します。

「P4 SPI」を選択します。

SPIを有効にしますかと聞かれるので、「Yes」を選択します。

SPIが有効になりました。

以上で、SPIが使えるようになりました。

続いて、MCP2515を使えるように設定します。
/boot/config.txtに、以下の設定を加えます。

「dtparam=spi=on」と書かれている場所のすぐ下に、
以下の赤字の部分を追記します。

dtparam=spi=on
dtoverlay=mcp2515-can0,oscillator=8000000,interrupt=25
dtoverlay=spi-bcm2835

今回使うCANモジュールは、MCP2515に8MHzの水晶、
INTをRaspberry Pi の GPIO25に接続するため、
上記の記述になります。

/boot/config.txtを保存して、Raspberry Piを再起動します。
$ sudo reboot

再起動後、dmesgで、デバイスドライバが動作していることを確認します。


あ、あれ、なんかエラーになってるっぽい。
そ、そうか、CANモジュールつないでないから、エラーになってるのかな?
CANモジュールが届くまで、なにも出来ないか・・・

これがうまくいっていれば、あとはsysfsにcanインターフェースを追加して、
終わりのはずでした。一応、書いておきます。
$ ip link set can0 type can bitrate 500000
$ ip link set can0 up

これで、/sys/class/net以下に、can0 というのが現れて、
このインターフェースを通して、通信ができるはずでした。
当然ながら、デバイスがないので、現状ではエラーになってしまいます。

アマゾンで配達状況を確認すると、「発送しました」となっているが、
中国からなので、いつ届くのだろうか・・・
早く来ないかなー。