2019年 10月 07日

NanoVNA WebApp の Android アプリ化

Capacitor をつかって NanoVNA-Web-Client を Android アプリ化してみた。

あんまりハマりどころはなかったのでそれほど書くことはないけど、いくつかメモ。

node_modules

アプリサイズを減らすためにできるだけリソースを減らす必要がある。node_modules 以下を雑に入れると数十MBになってしまうので、気をつける。

Web 版のリソースを完全にそのまま入れるより、必要なリソースだけをコピーするようなビルドスクリプトを書いたほうが良さそう。今回は Makefile でなんとかした。

splash 画像の生成自動化

Ionic のブログに Photoshop template があるので、これを利用させてもらう。

が、ブログの通り cordova-res を使うとうまくいかなかったので、Makefile と ImageMagick でなんとかした。

res:
	convert images/splash.png -gravity center -resize 480x320^ -extent 480x320 ./android/app/src/main/res/drawable/splash.png
	convert images/splash.png -gravity center -resize 800x480^ -extent 800x480 ./android/app/src/main/res/drawable-land-hdpi/splash.png
	convert images/splash.png -gravity center -resize 480x320^ -extent 480x320 ./android/app/src/main/res/drawable-land-mdpi/splash.png
	convert images/splash.png -gravity center -resize 1280x720^ -extent 1280x720 ./android/app/src/main/res/drawable-land-xhdpi/splash.png
	convert images/splash.png -gravity center -resize 1600x960^ -extent 1600x960 ./android/app/src/main/res/drawable-land-xxhdpi/splash.png
	convert images/splash.png -gravity center -resize 1920x1280^ -extent 1920x1280 ./android/app/src/main/res/drawable-land-xxxhdpi/splash.png
	convert images/splash.png -gravity center -resize 480x800^ -extent 480x800 ./android/app/src/main/res/drawable-port-hdpi/splash.png
	convert images/splash.png -gravity center -resize 320x480^ -extent 320x480 ./android/app/src/main/res/drawable-port-mdpi/splash.png
	convert images/splash.png -gravity center -resize 720x1280^ -extent 720x1280 ./android/app/src/main/res/drawable-port-xhdpi/splash.png
	convert images/splash.png -gravity center -resize 960x1600^ -extent 960x1600 ./android/app/src/main/res/drawable-port-xxhdpi/splash.png
	convert images/splash.png -gravity center -resize 1280x1920^ -extent 1280x1920 ./android/app/src/main/res/drawable-port-xxxhdpi/splash.png
	optipng $(wildcard ./android/app/src/main/res/*/splash.png)

Play Store のメモ

リリースしたことある気がしていたが、よく考えたら前職で数回やっただけなので個人のアカウントではやったことがなかった。いろいろストアの使い勝手が変わっていた。

  • 初回の審査は結構時間がかかる。一週間ぐらい? 更新の審査は割とすぐ。
  • 要求される画像がいっぱいあってだるい。全部埋めないとベータ版すらリリースできない
  • adb という仕組みができていた。(端末にあわせて必要なリソースだけを含む apk を自動生成する)

無料アプリの開発者視点からすると、別に Play Store に出すメリットはぜんぜんない。他の端末にさくっと入れたいときに便利かな、と思う。けど、それも普通にウェブサイト経由で .apk ダウンロードして入れればいいだけなので、それほど致命的に面倒くさいわけではない。まぁ Play Store のショバ代は初回に $25 なので Apple (毎年$99もとる) よりは格段に優しいといえる。

2019年 09月 29日

NanoVNA のユーザーガイド(マニュアル)を書いた

ある程度きちんと網羅されたのがなかったので自分で書いていた。コード読みながら書いてるので、誤解がなければ正しいことが書いてあるはず。とはいえ完全に網羅できてない (electrical delay まわりとかの記述がまるっと抜けている)。

2019年 09月 05日

中華 NanoVNA の Bluetooth シリアル化を試す

NanoVNA は USB-CDC による通信をサポートしていますが、ここが無線化すると(特にアンテナ調整の場合は)便利なので、コンセプトを試してみました。無線化といってもいろいろやりようは考えられますが、今回はシリアル通信を BLE SPP にのせかえる方法としました。

シリアルポートをMCUから引き出す

USART1_TX USART1_RX を MCU (STM32F072CBT6) から引き出します。中華 NanoVNA では NC になっている、それぞれ PA9 (USART1_TX) PA10 (USART1_RX) です。LQFP48 なので PA9 は 30ピン、 PA10 は 31ピンになります。

かなり細かいので面倒ですが、このピッチならギリギリなんとかなります。あまり接続部に負担をかけたくないので、ポリイミドテープで一旦うけています。

これをそのまま BLE 変換器に繋いでもいいのですが、デバッグがしにくいので一旦安定したところに繋ぎます。JTAG のピンヘッダがある部分に曲げたピンヘッダを追加して置いて、固定しました。

シリアルポートの有効化

NanoVNA のコード側の対応も必要です。

このパッチによって有効化しています。USB を接続した状態で電源をONにした場合は USB-CDC のシェルを有効にし、そうではない場合はシリアル経由のシェルを有効にします。

BLE UART 変換

手元に ESP-WROOM-32 が余っていたので、これを利用してみました。一旦雑に配線して変換プログラムを書きこんで、不要な配線をとって実際に組みこみます。

コードはこんな感じです。あまり好ましいとは思いませんが、RF 回路の突発電流で brown out detection (低電圧検出) にひっかかることがあるので検知を切っています。

#include "BluetoothSerial.h"
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"

BluetoothSerial SerialBT;

void setup() {
	WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);
	Serial.begin(115200);
	SerialBT.begin("NanoVNA");
}

void loop() {
	if (SerialBT.available()) {
		Serial.write(SerialBT.read());
	}
	if (Serial.available()) {
		SerialBT.write(Serial.read());
	}
}

デバッグの様子です。

組み込み

ピン名がわかりやすいからという理由で、ESP-WROOM-32 のシールド側を下にしています。シールドにはポリイミドテープを貼って念のため絶縁しています。これを無理矢理 STM32 の上に両面テープで貼りつけて固定し、配線しました。

一応これで納まりました。

懸念点

ESP-WROOM-32 の消費電力が非常に多いです。突発的に100mAぐらい平気で流れるので、本体の消費とあわせると 3.3V レギュレータの定格 (200mA) を若干オーバーしているかもしれません。

データが欠落します。BLE なのと、CTS/RTS を無視しているので仕方ないですが、データの欠落が結構起こります。

やっておいてなんですが、この方法は大変な割に微妙なので、ナシかなと思いました。古いスマフォでUSBを中継したほうが楽だし確実そうです。

9600bps ぐらいまでボーレートを落とせばデータ欠落はなくなるようです。が、本当に遅いのでやはり厳しいです。57600 だと少し欠落する。

BLE ではなく普通の Bluetooth SPP にすればいいんですが、普通の Bluetooth は Web Bluetooth API から呼べないのでやる気になっていません。

2019年 09月 03日

NanoVNA を WebUSB を使ってブラウザから見る

NanoVNA の USB のコミュニケーション方法が USB-CDC で、プロトコル自体は簡単そうだ、というのを前に書きました。なぜそんなことを調べていたかといえばブラウザでUIを作りたかったからなので、作りました。

機能

  • デバイスの現在のデータの読みこみとグラフ化(スミスチャート・周波数応答)
  • 複数のトレースタイプ (clear write / freeze / minhold / maxhold / videoavg / poweravg)
    • freeze が値の比較に便利だと思う。これが欲しくてトレースタイプを実装してます。他のはオマケ
  • ウィザード形式のキャリブレーション
  • デバイス画面のキャプチャ
  • デバイスバージョンの表示 (比較的新しいファームでなければ version コマンドがありません)
  • TDR (Time Domain Reflectometry)
  • s1p s2p 出力。ただし s2p は S22 = S11, S12 = S21 として出力する。

実装

ほとんどの機能は JavaScript で直接実装しています。TypeScript も使っていません。USB Device との通信は WebUSB で、できるだけポーリングを正確にしたいため Worker 内で行っています。このあたりは HackRF One を WebUSB から操作してスペアナを作るのと同じような手順です。

グラフは chart.js をそのまま使っているので工夫したところはありません。

現状では TDR 測定のところでだけ Rust で書いた実装を wasm にしたものを呼んでいます (TDR は RL をただ ifft しているだけです)。パフォーマンスが必要なわけではないのですが、複素数の計算を JS でやるのがとにかく面倒かつ可読性を損うので、Rust 資産を流用しという魂胆です。

本当は、Rust での処理は生dump データから、デバイスでやっている計算すべてをホスト側でやるのも見越してやりはじめましたが、一通りスキャンするのにかえって時間がかかりそうなのでやるのをやめました。ヒルベルト変換は fft/ifft でできるし、すぐ実装はできそうではあります。

備考:WebUSB と USB-CDC の将来は不安

WebUSB はそもそも既存のよくあるUSBデバイスに対してアクセス権限を与えるようなコンセプトのものではなく、USB-CDC も同様にスコープに入っていません。本来はSerial APIという提案仕様があって、こちらでカバーされるはずです。だけれど現状では実装されているブラウザが存在しません。

ということでやはりWebUSB でやるしかないわけですが、OS間で取り扱いが違うので意外と面倒くさいことがわかりました。

  • Windows では CDC 用のドライバがインターフェイスを握ってしまう
    • ドライバを libusb 用のものに置き換えれば使える
  • Ubuntu では cdc_acm ドライバがインターフェイスを握ってしまう
    • udev ルールで bind 直後に即 unbind することができる。

ドライバを入れ替えると普通のシリアルポートとしてOSには(当然だけど)認識されなくなるので、必要に応じて再び入れ替えたりする必要があります。

ここで一つ問題があって、現行の NanoVNA は USB の vid/pid を STM32 の CDC デバイスのもの(?)を流用しているため、上記のようなドライバ置き換えが他の STM32 デバイスに影響を及ぼすことがあります。vid/pid の関するイシューはあるのでそのうちなんとかなるかもしれませんが現状ではこの通りです。

なお macOS は実際のデータ転送に使うインターフェイスは、実際にポートがopenされるまで排他的に確保しないようで、WebUSB からも自由にアクセスできます。逆に WebUSB で握っている間にシリアルポートを別途開こうとすると、そのタイミングで resource busy が出ます。

現行の Android では特に何もせず、OTG コネクタさえ使えば接続することができました。ただ、中華NanoVNAはコネクタにType-Cを採用はしているものの、CC に何も接続されていないため、Type-C - Type-C ケーブルを使うとデバイスを認識しません。Type-A OTG 変換ケーブルなどを経由する必要があります。

とりあえず何が言いたいかというと、今のところ WebUSB で USB-CDC デバイスをなんとか動かす方法は存在しているけど、今後はどうなるかわからないということです。WebUSB 自体も (使われなければ) 消滅する予感のする仕様と感じています。少なくとも今の仕様 (パーミッションダイアログが危険性の割に雑) のまま広く使われることもないだろうと思います。

2019年 09月 02日

NanoVNA 左側の文字の意味

C0 → CAL0 の設定を読んでいる状態。
    c0 のように小文字の場合はエラー値の補完をしている状態
    (recall してから周波数範囲を変えた場合。厳密には uncal )
D → directivity エラー修正
R → refrection tracking エラー修正
S → source match エラー修正
T → transmission tracking エラー修正
X → isolation (crosstalk) エラー修正

それぞれの意味はエンハンストレスポンス校正あたりで検索する (自分はよく理解してない)

なお cal を done したときに、終わった calibruation menu のハイライトが一部消えるのは正常 (上記エラー情報を計算し終えると消える仕様)

2019年 08月 25日

NanoVNA の測定メモ

前につくったアッテネータを測ってみる https://lowreal.net/2016/03/13/1

NanoVNA だと 300MHz 付近にデコボコがあるようにみえますが、これはおそらく 0.5〜900MHz でキャリブレーションした状態で、0.5〜500MHz の範囲を見ている (校正の補完を使っている) せいと思われるので、再度この範囲でキャリブレーションしなおしてみます。(300MHz を超えると高調波モードを使うので段差ができやすい)

リターンロス (S11)

前回の結果の再掲 (TG つきスペアナ + リターンロスブリッジの結果)

挿入損失 (S21)

前回の結果 TGつきスペアナでの結果

2019年 08月 23日

中華 NanoVNA ってなんなのか? またはファームの歩きかた


NanoVNA という非常に小型で低コストのスタンドアロン VNA (ベクターネットワークアナライザ) がにわかにグローバルで流行している。VNA は高周波回路設計・実装に必須の測定器のひとつだが、もともと非常に高価なため、個人でちゃんとしたメーカーものを所有することはまずない。たとえ低価格と言われるものでも数万円〜だった。それがさらに低コストで使える性能であると評判なので、VNA の民主化だというおもむき。

NanoVNA の設計のオリジナルはTT (ttrftech) さんという日本語でブログも書かれているかたの設計のもの。自分もハムフェアかMaker Faireかで実機をちらっと見たことがある気がする (もしかするとVNAではなく後続プロジェクトのSDRのほうだったかもしれないが)。少量のキット化までされていたが、これを hugen79 さんが手を加えてしPCBアートワークなどをやりなおして売っているようだ (もともとPCBの設計はソースに含まれてない)。ファームにも独自のコードが入っている。

aliexpress などでは、hugen79 版を元にした (おそらく) さらにこれのクローンと思われるものが非常に安価に売っている。hugen79 レポジトリの README では、粗悪なものがあって性能が出ないことがある (ミキサーまわりに齟齬があるらしい? シールドの有無など違いもある) けど、本来の性能じゃないから買う側でちゃんと調べろや、ということが書いてある。

自分には技術レベル的にVNAは必要ないが、VNAとして使わなくても、これはそのままアンテナアナライザーにもなるわけで、もはや自作するより良さそうだ。

ちなみに2ポートのうちCH0だけ反射を計測できるので、繋ぎかえて測定してマージしないと s2p 相当の結果を得られない。(S11 S21 を得て、繋ぎかえて S11、S21をS22 S12 に読み替える) 入出力が全く同じで対称の受動コンポーネントなら S22=S11 S12=S21 としてもまぁいいのかも (VNA使ったことないので温度感がわからない)。

基本的な使いかた

  • 周波数の範囲を決める (範囲内の固定 101 ポイントを測定する)
  • キャリブレーションする
    • RESET してから、CAL を順番に実行していく
  • 被測定物を繋ぐ

特定のよく使う周波数範囲に関してはキャリブレーション済みの状態を保存できる。

PCソフトウェア

オリジナル版だと python による実装があり Jupyter Notebook から呼びだすような例が書いてある。

それとは別に hugen79 開発の C# ソフトウェアが存在している。ただしこれはOSSではない。

デバイスとの通信は USB CDC によるもので特に難しさはなさそう。main.c を見れば実装はわかるし、python 実装を読めばホストインターフェイスは容易に書けそう。ただ細かいことをやろうとするとホスト側で信号処理をやらないといけない。

逆に USB CDC しかないので雑に BLE シリアル化みたいなのはちょっと面倒そう。MCU から使ってないピンを引き出してUARTに割り当てたらいいのかも。

ファームウェア

販売されているものに入っているファームウェアは店によって違うっぽい。hugen79 ファームが入っているようだが、これにはいくつか種類がある。上限周波数とアンテナアライザーに特化しているかどうかの違いらしい。いまいち違いがわからないのでコードを読んでみたが、上記レポジトリには上限周波数のフラグはあってもアンテナアライザー特化版フラグが入ってなさそう。謎。

ttrftech ファームも割と最近には上限を900MHz までとする高調波拡張が入っているみたい。(ブランチには1.5GHzまでの拡張もある)


ということで何らかの変更を入れる場合どっちからフォークするかは悩ましい。github を検索すると FreeRTOS バージョンのコードもあったりする。hugen79 版はオリジナルから定期的にコードを手動でマージしているみたいなので、特に必要がなければオリジナルにPRを作ったほうが良さそうかな。

SRAM がギリギリなのであまり高級な機能は入れにくい。可能なら宣言済みのバッファをうまく利用したい。

ファームウェアの歩きかた

ブロックダイアグラムをまず見とく。全体でやってることは難しくなくて、クロックジェネレータ・ミキサ・ステレオオーディコーデック・MCU をうまく組み合わせてあまり部品数を増やさずに構成されている。

MCU は STM32F072C8T6 (Coretex-M0 48MHz / 16KB SRAM / 64KB Flash)


STM32F072CBT6 (Coretex-M0 48MHz / 16KB SRAM / 128KB Flash) でした。

ファームウェア側は RTOS として ChibiOS が採用されている。

シェルまわり

USB CDC のシェルは ChibiOS の shell 機能で実装されている。main.c に定義がある。

すべての機能がシェルから呼べるようになっているので、機能から見るなら基本は main.c を起点にして該当コードを探すのが読みやすいと思う。

信号入力まわり
  1. TLV320AIC3204 (ADC) を初期化
  2. STM32 の I2S は常時起動しており i2s_end_callback() が呼ばれている
  3. dsp_process() で現在の値をある程度計算してグローバルに保持する

があったうえで、別スレッドで

  1. オシレータの周波数を設定
  2. ADC のチャンネルを選択して DSP が安定するのを待つ
  3. calculate_gamma() でΓを求める

がスイープにあわせて実行されている。

レンダリングまわり

ベクトルネットワークアナライザNanoVNAの液晶画面を実装する このエントリにおおまかな実装概要が書いてある。コードは plot.c だがメモリが少ないのを工夫して実装してあるので難しい。ちゃんと読んでない。トレース結果とかの状態をグローバルに保持しつつ、再描画が必要な領域に dirty フラグ (mark) を立てて、draw_cell() が実際に特定の領域のピクセル情報をつくってディスプレイに送っているみたい。

ref

NanoVNA に capture コマンドを追加してみる

NanoVNA の実装を軽く読んだ感じでは画面のキャプチャをとる機能がなさそうだったので、実装を書いてみた。測定器はとにかくキャプチャをとりたいし、せっかく綺麗にレンダリングされているので、できれば保存したい。

中華 NanoVNA ってなんなのか? またはファームの歩きかた | tech | nanovna - 氾濫原 にも少し書いたけど、画面描画はメモリ消費を抑える実装になっており、MCU 側で画面バッファを全て持っているわけではない。

そこで、使っているLCDドライバの ILI9341 のデータシートを見てみたところ、ドライバ側で持っているメモリ内容を SPI 経由で読み出せそうであったので、そちらを利用した。

メモリ内容を読めるといっても、MCU 側のメモリ容量的に全てを一気に読むことはできないので、一部読んではUSBに流し、一部読んではUSBに流すというのを繰り返す実装にした。

ハマったところ

SPIを受信するコードから書く必要があったけど、STM32 に慣れていないせいでかなりハマってしまった。

まず FRXTH フラグを適切にセットしていないと RXNE フラグがセットされないので RXNE を見て無限ループさせるとそこでスタックする。

あとは (おそらく) 受信バッファ溢れ (オーバーフロー OVR )の場合で、受信バッファが溢れた場合、あとからきたデータで上書きされるのではなく、単に全て捨てられるようなので、執拗に OVR をクリアするような実装を書く必要があった。もっとスマートに書けるのかもしれない。