Julia で LimeSDR を制御するまでの道のり
この記事のまとめ:
- LimeSDR 用の C++ のサンプルコードを動かします
- Julia で
ccall
を使ったサンプルコードとしてRTLSDR.jl
を動かします - Julia で Lime Suite の C/C++ 用のライブラリー関数を
ccall
で呼び出します Clang.jl
を使って Lime Suite の C/C++ 用のライブラリー関数のラッパー関数を自動生成し、パッケージ化します- 自作のラッパー関数のパッケージを読み込み LimeSDR mini で信号受信のサンプルコードを動かします
背景
普段仕事で MATLAB を使うことがあるのですが、処理の遅さやライブスクリプトの使い勝手の悪さが以前から気になっていました。MATLAB の代替言語と考えたときに数年前から Julia 言語が気になっておりまして、ようやく重い腰を上げて、まずはプライベートで Julia を本格的に学び始めているところです。 それと同時に、せっかく LimeSDR mini を(だいぶ前に)購入したのに、これまでは誰かが書いたコードを動かすだけでした。SDR (Software Defined Radio) なのに自分でプログラミングできないのはもったいないかと思い、自分である程度使えるようになっておこうと思ったところでした。
そういった背景があり、Julia 言語の勉強と SDR の勉強を兼ねて、LimeSDR の C 言語用 API の Julia 用のラッパーを作ってみました。
正直、Julia は初心者ですし、C 言語のポインターも高校から大学のときに覚えたくらいの知識ですし、LimeSDR でプログラミングの経験もないので間違いなどあればご指摘いただけると幸いです。
はじめに
次の5ステップで LimeSDR mini を Julia でプログラミングしていきます。
- Step 1: C++ で LimeSDR 用のライブラリー関数を呼び出す
- Step 2: Julia で C ライブラリー関数を呼び出す
- Step 3: Julia で LimeSDR 用のライブラリー関数を呼び出す
- Step 4: Clang.jl で LimeSDR 用のライブラリー関数の Julia 用のラッパー関数を自動生成し、パッケージ化する
- Step 5: Julia で LimeSDR 用ラッパー関数を使用して LimeSDR を制御する
ここで紹介するサンプルコードはすべて次のリポジトリーにあります。 サンプルコードはすべて Linux 上で確認しています。 Step 1 - 5 をそれぞれ異なる Docker コンテナーを立ち上げ、動かしています。 コンテナーの立ち上げには docker-compose を利用しています。 Docker 関連の操作方法の説明は、本記事では十分にはしませんので、ご了承ください。
hassiweb-programming / LimeSDR Test in Julia Julia で LimeSDR を制御するためのサンプルコード
Step 1: C++ で LimeSDR 用のライブラリー関数を呼び出す
まず Step 1 として、LimeSDR mini を使って自分でコーディングする感覚をつかんでみます。
LimeSDR を制御するためには、Myriad RF から提供されている Lime Suite を使います。 Lime Suite には LimeSDR 用のドライバーや C 言語と C++ 用のライブラリー関数 (API) などが含まれています。 まずはこの API を C++ でサンプルコードを使って動かしてみます。
C 言語や C++ で書かれたサンプルコードを探してみると次のようなものがありました。
- Myriad RF のディスカッションで共有されていたサンプルコード
- csete/limerx.c
- LMS API - Quick start guide - (Myriad RF のページでは出てこないのが不思議…)
ここでは細かい動作を確認するつもりはありませんので、一番シンプルな 1. のサンプルコードを実行してみます。 なお、このサンプルコードは送信機用であるため、実行すると電波発射をします。 そのまま空間に電波発射すると電波法違反になりますので適切な環境を用意して実行してください。
サンプルコードを実行するために必要なパッケージは Lime Suite と g++ くらいです。 それぞれ Ubuntu であれば apt パッケージマネージャーでインストールできます。
サンプルコードを /apps/lime_test.cpp
としたとき、コンパイルは次のようにできます。
ここでは、オプションとして -lLimeSuite
で Lime Suite を読み出していることがポイントです。
なお、このサンプルコードでは、送信ストリームの処理にスレッドを使っているため -pthread
オプションを付けています。
生成されたバイナリーファイルを次のように実行すれば、LimeSDR が電波発射します。
そのままでは動いているのかわからないので、RTL-SDR に有線接続してスペクトルを確認してみました。 どうやら指定した周波数で何かを送信していることは確認できたので、とりあえず C++ でのサンプルコードの実行は完了です。
Step 2: Julia で C ライブラリー関数を呼び出す
続いて Step 2 として、Julia で C 言語用のライブラリー関数を呼び出すコードを見てみます。
Julia で C ライブラリー関数を呼び出すのは比較的簡単だといわれています。 ただ、私はその経験がないのでその感覚からつかんでみようと思います。 C ライブラリー関数を呼び出す良いサンプルがないか探してみたところ、Julia 用の RTL-SDR ラッパーがあったのでそれを試してみます。
Julia を簡単にテストするには Jupyter Notebook を使うのが便利です。 以前、Docker で Julia が使える Jupyter Notebook コンテナーの立ち上げ方を紹介しました。
基本的には以後のステップでもこの jupyter/datascience-notebook をベースとして Dokerfile
や docker-compose.yml
編集してステップごとの用途に合わせて使っていきます。
RTL-SDR を使える Julia 環境の準備
まずは、Jupyter Notebook が使える Docker イメージに RTLSDR.jl
を使える環境を整えます。
Dockerfile
内で RTL-SDR 用のライブラリーのインストールを記述し、packages.jl
で RTLSDR.jl
のインストールする記述を追記します。
また、この Docker イメージを立ち上げる際、RTL-SDR の USB ドングルをコンテナー内からアクセスできるように docker-compose.yml
に devices
のオプションを追加します。
そして、この docker-compose.yml
で Docker コンテナーを立ち上げると RTLSDR.jl
を使える Jupyter が立ち上がります。
なお、 Docker コンテナーを立ち上げる際は、必ず PC に RTL-SDR を接続をしてから立ち上げるようにします。
RTLSDR.jl の動作確認
ブラウザーで Jupyter Notebook を開きます。
まずは RTL-SDR 用のライブラリーである libstlsdr
が読み込めるか確認します。
Libdl
モジュールは共有ライブラリーを読み込むためのモジュールです。これを使って libstlsdr
が読めるか確認します。
次のように表示されれば、ライブラリーが読み込めています。
早速、RTL-SDR を使ってスペクトルを確認してみます。帯域幅が 2 MHz で、中心周波数が 88.5 MHz の設定だと次のとおりです。
うまく動作すればこのようにグラフが表示されます。
ccall の使い方を確認する
RTLSDR.jl
がどのように C のライブラリー関数を呼び出しているか確認するためにソースコードをのぞいてみます。
ソースコードはこちらにあり、 RTLSDR.jl
と c_interface.jl
の2つのファイルで構成されています。c_interface.jl
で C 言語用に用意された librtlsdr
を ccall
メソッドで呼び出す Julia 用の C ラッパーの関数群です。RTLSDR.jl
がそれらの関数を利用して使いやすい関数として定義しているようです。
たとえば、RTLSDR のデバイスポインターを取得する関数がこちら。
ccall
で librtlsdr の rtlsdr_open
の関数を呼び出していることがわかります。
ccall
の用法は公式マニュアルを読み込む必要がありますがこんな感じで C ライブラリーを呼び出せることがわかりました。
Step 3: Julia で LimeSDR 用のライブラリー関数を呼び出す
ここまでで、Lime Suite の使い方と、Julia で C ライブラリーの呼び出し方がわかってきました。 ここからは、本来やりたいことであった Julia で Lime Suite の C/C++ 用 API を呼び出していきます。
Step 3 では、もっともシンプルな方法で Julia で Lime Suite の C/C++ 用 API を呼び出します。
Lime Suite を使える Julia 環境の準備
まずは、Docker で Julia と Lime Suite を使える環境を用意します。
Step 2 と同様にjupyter/datascience-notebook をベースに Dockerfile
内で Lime Suite をインストールします。
さらに Step 2 と同様に Docker コンテナーを立ち上げる際にコンテナー内から USB で接続された LimeSDR mini にアクセスできるように docker-compose.yml
内に devices
オプションを追加します。
準備ができたら docker-compose
コマンドでコンテナーを立ち上げます。
ccall
を使って API を呼び出す
まずは、RTL-SDR のときと同様に Lime Suite のライブラリーが正しく読み込めるか確認します。
"libLimeSuite"
と表示されればライブラリーが正しくインストールされていることがわかります。
それでは LimeSDR の API を呼び出していきます。
Step 1 で最初に呼び出す API でもある LMS_GetDeviceList
を呼び出すラッパーを作ってみます。
マニュアルを見るとこの API の型は次のように定義されています。
lms_info_str_t
型は char (8 ビット) 型の 256 要素の配列で、そのポインターを渡すということは配列の配列を扱うことを意味しています。
公式マニュアルによると Julia で C に引き渡す変数を定義する際、C のサイズが決まっている配列に相当する Julia の型は NTuple
として定義するようです。
また、その配列の配列のポインターを渡すために Array
型で定義します。Array
型を ccall
で引き渡すと自動的にポインターを示す Ptr{T}
の型に変換してくれるようです。
これらを記述すると次のとおりです。
それでは実行してみます。
num_dev
には利用可能なデバイス数が入ります。
dev_list
も値が更新され、最初のデバイスを指す dev_list[1]
を文字列として表示させています。
正しく実行できていれば、次のように表示されます。
これでようやく自分で書いた Julia のコードで LimeSDR を制御できてきました!
なお、この実行時に次のような USB へのアクセス権に関するエラーが出る場合があります。
これは、Jupyter 用の Docker コンテナー内では、root ユーザーではなく jovyan
ユーザーでコマンド実行をしているためです。
そのため、あらかじめホスト側で対象のデバイスに対してアクセス権を与えるようにしてください。
Step 4: Clang.jl で LimeSDR 用のライブラリー関数の Julia 用のラッパー関数を自動生成し、パッケージ化する
Step 3 で時間のかかるタスクの1つは、API を記述した C ヘッダーファイルから ccall
でラップする Julia 関数を作成することです。
Clang.jl
を使うと、この作業を自動化できます。
Clang.jl
モジュールには、C ヘッダーファイルから Julia ラッパーを作成するジェネレーターが含まれています。
現在、以下の宣言がサポートされています。
- function: Julia に翻訳された ccall (va_list と vararg 引数はサポートされていません)
- struct: Julia に翻訳された構造体
- enum: CEnum と訳されます
- union: Julia 構造体に翻訳されています
- typedef: Julia に翻訳された typealias をもとにした固有型
- marco: 限定的なサポート (
src/wrap_c.jl
を参照)
Step 4 では、Clang.jl
を使って Lime Suite のラッパーモジュールを含むパッケージを作ります。
なお、Clang.jl
のドキュメントは多くないですが公式ドキュメントや次のサイトを参考すれば、概要はわかるでしょう。
Clang.jl
を使うにあたってリファレンスコードになるようなプロジェクトを探したのですが、私が探した限りでは、Clang.jl
の古いハイレベル API を使ったコードばかりで最新の Clang.jl
を使った実装は見つかりませんでした(たとえば、SCIP.jl
)。
こちらの issue にあるとおり、Clang.init
や Clang.run
といった関数で実装を行っているものは古い Clang.jl
の実装を利用しています。
現状、動作に問題はありませんでしたが、将来的に廃止される可能性もありますので、最新の実装を行ったほうがよいと思います。
Clang.jl を使える Julia 環境の準備
Julia の実行環境として、引き続き jupyter/datascience-notebook をベースにしますが、今回は Jupyter は使わず REPL を主に使います。
まずは、Clang.jl
をインストールするために、Package.jl
に Pkg.add("Clang")
と追加してあります。
また、Clang
を実行する際、stddef.h
がないというエラーが出たため、 Dockerfile に次の部分を追加しています。
準備ができたら docker-compose
コマンドでコンテナーを立ち上げます。
Clang.jl を使うときのパッケージ構成
μβ の記事にも書いてありますが、Clang.jl
でパッケージ化する場合、ディレクトリー構成として次のように gen
ディレクトリーに Clang.jl
の実行コードを保存します。
今回は、パッケージのビルドや、テストコードは用意しませんので、それ以外のものを作っていきます。
Clang.jl 用プロジェクトの作成
パッケージの初期化は Julia の REPL からパッケージ管理モードに入り、次のように generate
コマンドで作成できます。
コマンドを実行すると次のようなファイル類が作成されます。
ここから gen
ディレクトリーを作成し、そのディレクトリーで Clang.jl
用の作業をしていきます。
Clang.jl
は LimeSuite 用のパッケージを動かすためには不要なため、gen
ディレクトリー内だけで Clang.jl
を動かすだけのためのプロジェクトを作成します。
gen
ディレクトリーでパッケージ管理モードに入り、次のように打てばカレントディレクトリーでプロジェクトを作れます。
Project.toml
が生成されますので、ファイルを開き、プロジェクト名として次のように記述します。
Julia の REPL を立ち上げる際、次のようにオプションを付けるとカレントディレクトリーの Project.toml
を読み込んで Julia の REPL を立ち上げてくれます。
Project.toml
には依存モジュールなどの情報が記載されます。
たとえば、gen
プロジェクトで次のように Clang.jl
をモジュールを読み込むと、自動的に Project.toml
に依存モジュールとして書き込まれ、管理されます。
次回以降 gen
プロジェクトを読み込むと依存モジュールを自動的に読み込んでくれます。
Clang.jl を使ったラッパーの自動生成
あとは Clang.jl
を使ったラッパー生成のコードを作成します。
リファレンスとして、上記で紹介した公式ドキュメントのサンプルコードをほとんどそのまま使います。
コードは generate_wrapper.jl
としてここにあるとおりですが、Lime Suite のヘッダーファイル群のパスを変更したり、ファイル名やパスを変更したくらいでほぼサンプルコードのままです。
なお、複合型 (struct
) は C の構造体のように使うため、mutable
として定義するためのオプションを次のように設定しています。
REPL でこのコードを実行すれば、src/wrapper
ディレクトリーにラッパー関数が自動生成されます。
なお、REPL でソースコードをのファイルを実行する場合は、次のように include
を使えばよいです。
これで Clang.jl
でパースできた構文については自動的に Julia 用のラッパー関数のファイルを作成してくれます。生成されるファイルは2つあり、関数定義ファイルである libLimeSuite_api.jl
と変数や型の定義ファイルである common.jl
です。
ただし、次のようなものは生成してくれませんでした。
- タグなし enum 型
- enum 型を含む構造体
- static 定数
これらについては、手動で記述が必要です。
今回は manual_common.jl
として定義しています。
なお、C の enum 型に対応する型を扱う場合は、CEnum.jl
モジュールを読み込む必要があります。
パッケージを完成させる
パッケージを初期化した際に生成されるパッケージ名の Julia ファイル(今回の場合は LimeSuite.jl
)にパッケージとして使用するファイルを記述します。
Step 5: Julia で LimeSDR 用ラッパー関数を使用して LimeSDR を制御する
Step 4 で作成した LimeSuite.jl
パッケージは GitHub で公開しています。
これをモジュールとして追加して、LimeSDR のテストを行います。
LimeSuite.jl を使える Julia 環境の準備
Docker イメージのベースはほかのステップと同様 jupyter/datascience-notebook を使います。
Packege.jl
に次のように記述して、step 4 で作成したパッケージをインストールした Docker イメージを作成します。
自分が作ったパッケージをこうも手軽に公開して利用できるって本当に便利ですね。
LimeSuite.jl の動作確認
Step 1 で紹介した LMS API - Quick start guide - に記載されているテスト用の 8-PSK 信号を受信するコードを Jupyter Notebook で書いていきます。
ただ、ひとつひとつ説明すると長くなるので、書いたコードはこちらに載せておきます。
最終的にはこのように PyPlot を使って表示できました。
Julia での C 用のポインターの扱いが難解で苦労したとことが多々あり、これはこれでいつか記事にまとめようと思います。
だいーーぶ長くなりましたが、今回は以上です。 最後まで読んでいただき、ありがとうございます。
関連記事
RTL-SDR を使った簡易スペアナ RTL-SDR を使って簡易スペアナとして使えるソフトウェアをいくつか試しています。日本の通信事業者の割り当て周波数をまとめています。
コメント
コメントを投稿