3000円強ぐらい(送料込みで5000円ほど)で売っている精密バイス。普通精密バイスは安くてもこの3倍、ちゃんとしたやつは10倍ぐらいの価格がする。いわゆる浮きあがり防止クイックバイス (横と下に締める力が働く) で、固定部の直角が出ているというもの。

浮きあがり防止クイックバイスの良い点はでっぱりが少ないという点で、普通のハンドルがついてるタイプよりも干渉がすくない。

スペックは

  • HRC58-62 (20CrMnTi)
  • 平行度 0.005mm/100mm 直角度 0.005mm
  • 全高 50mm 全幅 50mm 全長 135mm 最大開き 65mm 口金高さ 25mm
    • 実測 全高 50.2mm 全幅50.1 全長140.7 最大開き 66.3mm 口金高さ 24.9mm

箱や検査票などは一切ついてない。

ぱっと見でおかしいのはボルトがついている斜めの面が0.3mmぐらい傾いてる。まぁ機能にはあまり関係ないだろう… わかるようなガタはなく、摺動もスムーズ。

可動部と固定部は横から見た限りではぴったりになるように見える。ただ各部の直角度はかなり怪しい。お勧めできるかというとできなそう。

  1. トップ
  2. tech
  3. QKG50 という精密バイス


スピンドルスピードを実測するやつを作った。左から、実測・設定値・最大値 (プーリー設定によって変化)。再現性とか考えるとやはり実測回転数が必要。

回路



PCB Milling でさくっと実装

R3 が感度を決めているが、これは一旦可変抵抗をつけて調整してから、調整済みの抵抗値を実測し、チップ抵抗に換装する方法にした。可変抵抗そのままつけたほうがいいけど小さくしたかったので

Fusion360 で治具作成

3D プリンタで出力。単に非接触で固定するだけなので、締結は M2。

テスト

ベンチトップオシロと離れててプローブが届かないのでバッテリ式のオシロで確認。

Machinekit config

# hal

# spindle encoder
setp hpg.encoder.00.chan.00.scale 1
setp hpg.encoder.00.chan.00.counter-mode 1
setp hpg.encoder.00.chan.00.A-pin 7
# postgui.hal

setp scale.2.gain 60
setp lowpass.0.gain 0.010000
net spindle-velocity => lowpass.0.in
net spindle-fb-filtered-rps      lowpass.0.out  => abs.0.in
net spindle-fb-filtered-abs-rps  abs.0.out      => scale.2.in
net spindle-fb-filtered-abs-rpm  scale.2.out    => pyvcp.spindle-speed

1pulse/rev なので scale は 1 になる。4箇所ぐらいに反射テープ貼って4pulse/rev にしてもいいかもだけど

過去の試行

前は頑張って60pulse/revなエンコーダを作ろうとしていたけど、かなり面倒なので、単純に1pulse/revをフォトリフレクタで受けるようにした。

  1. トップ
  2. tech
  3. Sable-2015 + Machinekit (LinuxCNC) スピンドルスピード

Probing the magnetic field probe. Roy Ediss, Philips Semiconductors, UK. というドキュメントが良くできていて、これにある程度従って自分でも追試してみた。

8mm

Aliexpress で買ったやつ。どのタイプかはよくわからない。8mm は小さい

写真の通り、測定は端を50Ω終端した接地コプレーナ線路の漏れ磁界を拾うことでやってみた。

20mm


↑ 20mm central gap 2(a) タイプ


↑ 20mm gap at neck 2(c) タイプ


↑ 20mm king type 2(d) タイプ

すべて RG405 を自分で曲げて作ったもの

20mm で 1.5GHz スパンだと変な共振は見えない。gap at neck タイプは周波数特性のフラットネスが悪い。

追記: central gap 2(a) タイプは 3GHz スパンで見ると 1.8GHz あたりで共振がみえた。

35mm


↑ 35mm central gap 2(a) タイプ


↑ 35mm king type 2(d) タイプ

これも RG405 を曲げて作ったもの。central gap タイプを作って測定し、king type に作り変えた。

35mm まで大きくすると、central gap タイプでは 960MHz あたりに共振が発生しているのが見える。king type ではそれがない。

80mm 根本で接続 2(c) タイプ



↑ 80mm gap at neck 2(c) タイプ


↑80mm king type 2(d) タイプ


5D-FB だったかな? 昔作ったものと作りかえたもの

ref

  1. トップ
  2. tech
  3. 近磁界プローブの種類

Aliexpress で300円ぐらい。ステレオのグラウンドアイソレータ。結構小さくていい感じ。内部的には(開けてないけど)トランスが2つ入っているはず。

広域ノイズを入力して通過した信号をオシロスコープで FFT して見てみた。入出力に導通がないのはチェック済み。

ノイズ源のせいか 22kHz まではあまりよくわからなかった (トランスなら低域で減衰があるはずだけど) ので、使わないが 500kHz まで広げてみると以下のような感じで300kHzぐらいに共振が見える。トランスの測定方法がよくわからないので、測定方法 (入出力インピーダンス) のせいかもしれない。

  1. トップ
  2. tech
  3. 3.5mm ジャック・グラウンドアイソレータ


ミツトヨ(mitutoyo) レクタンギュラゲージブロック セラミックス製 0級 25mm 613635-02 - ミツトヨ(Mitutoyo)

ミツトヨ(Mitutoyo)

5.0 / 5.0

なぜか2600円ぐらいで売ってたので買ってしまった (単品)。0.01μm オーダーで精度が出ている 25mm のブロック。ジルコニアセラミックでできていて

  • サビない
  • 経年変化しない
  • 摩耗しにくい

ので個人でも扱いが比較的楽。熱膨張係数は 9.3±0.5 (10^-6/K) で鋼の熱膨張係数 (10.8±0.5) と近いので、同様の雰囲気に置いておけば20±4℃ぐらいなら 100mm でも誤差が1μm未満に収まる。(ただしセラミックのほうが熱伝導率が低いので、より長い慣らし時間が必要)

マイクロメータの検査に使える。このマイクロメータは出荷時検査で 25mm のとき +1μm なので、これであってる (マイクロメータ側の誤差)。

ゲージブロックは絶対に個人ではいらないレベルのものだけど文明が滅びても正確な長さを示してそうなので気分が良い。

  1. トップ
  2. tech
  3. 25mm ゲージブロック

機械式(バネ式)のエッジファインダー (芯出しバー)。Φ6のものは少なくて若干高価。1000円ぐらい。材質は非磁性、チタンコートとして書いてなかった。

精度は 0.003mm らしい。

太さの計測


先端の精度は完璧。接触で使うので多少摩耗していくだろうから最初に測っておくのは大事

使いかた

600〜800rpm でまわしながら使う。ワークから離れたところでは自由なので先端がブレて動く。ワークに近付くにつれてブレが収まり、ある点を超えると急に大きくブレる。この急に大きくブレた点がワークと接触したところ。

手順としては一度あててから戻し、ゆっくりあてなおす。急に動いたところで止めて、半径をオフセットさせて原点を設定する。

これだけ小さくてもZ軸の余裕がない機械だと結構ぎりぎりになる。

備考

電子式のエッジファインダはΦ20〜しかない。機械式で最小のものはΦ6、通常はΦ10のようだ。

  1. トップ
  2. tech
  3. Φ6 エッジファインダー

プーリー駆動の付け替えで、24V 時 10000rpm と 4300rpm を切り替えられるということになっている。自分の環境ではこの加え、PWM コントローラによる電圧可変である程度回転数を変えられる (M3 S5000 みたいな) ようにしている。

pru_generic の pwm_period

BeagleBoneBlack + Machinekit で動かしていて、PWM 出力は pru_generic のものを使っている (ハードウェアPWMではない)。PRU の実行サイクルは 100kHz (10000ns) ごとなため、あまり高い周波数で PWM 出力ができない。

使っている PWM コントローラの入力周波数は1kHz〜10kHz 周期でいうと 1000000ns-100000ns なので、1kHz のPWM周波数でやれば分解能が最も高くなる。が、PWM 周波数が可聴域の高感度な周波数だとうるさいので悩ましいところ。

なんとなくやる気が沸いたのでプーリーの設定を machinekit 側 GUI で切り替えできるように作りこんだ。回転数計もそのうち(再び)組込みたい。

実測

pwm_period を変えたケースでも試してグラフ化してみた。オープンループ・フィードバックなし負荷なしでただ回しているだけなのでここにさらに切削抵抗が加わると減速する。

  • 使っているコントローラのせいか、最大電圧が22Vぐらいなので本来はもうすこしスピードが出ると思われる
  • あまりリニアに変化していない
  1. トップ
  2. tech
  3. Sable-2015 のスピンドル回転数

HS2234 という名前のレーザー式タコメーターをAliexpressで買った (1300円弱)。2.5rpm〜99999rpm まで測れるという触れこみ。電源が単4電池なので(この手のありがちなのは9V電池)ちょっと優しい。

接触で測ることはできず、非接触のみ。

  • 対象までの距離 50mm〜500mm
  • 精度 ± 0.05% rdg + 1 digital

特に使いかたで難しいところはない。説明書 (中国語と英語) がついてくる。おそらくあまり複雑なことはせずパルスをカウントしているだけなので、再帰反射テープはつけないとダメっぽい (付属する)。

あんまり近付けると受光部にレーザー光が入らないので気をつける必要がある。

カウント精度チェック

シグナルジェネレータで duty cycle 10% のパルスを発生させ、LED を光らせて HS2234 の受光部にあてる。

rpm は周波数に60を乗じるので、100Hz のとき 6000rpm になる。

  • 1666Hz (99960rpm) → 表示 99954
  • 1500Hz (90000rpm) → 表示 89995
  • 1000Hz (60000rpm) → 表示 59997
  • 167Hz (10020rpm) → 表示 10019rpm
  • 100Hz (6000rpm) → 表示 5999rpm
  • 1Hz (60rpm) → 表示 59.9rpm
  • 0.5Hz (30rpm) → 表示 29.9rpm
  • 0.1Hz (6rpm) → 表示 5.9rpm

パルスが理想的なら十分な精度そう。なんかどうも必ず-1カウント低い値が出ているような気もするが……

表示が安定するまで若干時間がかかる。0.5s ごとに表示を更新するみたいだけど5秒〜30秒ぐらいやらないと安定しない。

回転計のアルゴリズムの考察

この回転計はおそらく単純にパルスをカウントして演算しているだけなので、周波数カウンタと同様の原理といえる。

理想をいえば反射材からのパルスではなく波形の繰り返し成分から周波数成分を見つけてほしい。FFT して成分解析すればできそう、だけど言うほど簡単ではないのだろうなあ。

  1. トップ
  2. tech
  3. HS2234 レーザータコメータ (回転数計)

Beagle Bone Black + Machinekit での CNC 制御 (Sable-2015)で機械仕様から各軸のSCALEを求めていたが、(いまさら) テストインジケータを買ったのであらためて計測して誤差を修正することにした。

元々の SCALE は

200 * 8 * (1/1.5) #=> 1066.6666666666665

X=-1066.667
Y=1066.667
Z=1066.667

Z軸

誤差±0.01/0.4mm

修正不要と判断してそのまま

X軸

誤差-0.01mm/0.4mm (2.5%)
バックラッシュは0.005mmぐらい

SCALE =  -1093.333333

Y軸

誤差-0.01mm/0.4mm (2.5%)
バックラッシュが0.01mmある

SCALE =  1093.333333


そもそもXとYの直交が出てるのかとかも測定して調整しなおしてみたいけどやってない

Sable-2015 は全体的にアルミなので、マグネットベースをとりつけられるところがモーターぐらいしかなく結構やりにくい。

  1. トップ
  2. tech
  3. Sable-2015 のキャリブレーション

前にちょっと書いたけど、水晶発振子のモデル化のための測定についてもうちょっと詳しく書いておく

LCRメータでCpを測る

Cp=1.95pF @100kHz

VNA で測ってもいいけどキャリブレーションが面倒なのでLCRメータを使うのが楽 (値が小さいのでLCRメータでもキャリブレーションはすること)

VNAでfs/fp/Rsを測る

fs/fp


VNA のポート1ポート2に直列で水晶発振子を繋いで、共振周波数付近をさがす。最もゲインが大きいところがfsで、小さいところがfpになる。

fs=9.9985Mhz
fp=10.0126Mhz

Rs

VNAのポート1に水晶発振子を繋いで、直列共振周波数 (fs) 付近で測る。fs 付近だと(共振しているので)水晶が純抵抗に近くなり精度が高くなる。

Rs=12.8Ω

Cs/L を求める

以下のように Cs L を求める

f_s = 9.9985
f_p = 10.0126
R = 12.8
C_p = 1.95e-12

C_s = C_p * ( (f_p**2) / (f_s**2) - 1)
L = 1 / (4 * (Math.PI ** 2) * (f_s ** 2) * C_s)
console.log({C_s, L}); //=> { C_s: 5.503702932046284e-15, L: 0.04603790760936647 }

LTSpice でモデル化してみる

求められた R/Cp/Cs/L を使って等価回路を作り、周波数特性を見てみる。

LTSpice の結果は信号源の出力に対する比なので、負荷の電圧を見る場合にVNAとスケールをあわせるには2倍 (電圧 +6dB) する。

  • LTSpice: -1.04dB
  • VNA実測: -1.31dB

ダイナミックレンジの関係で並列共振周波数の値は参考にしかならない。

ref

  1. トップ
  2. tech
  3. 水晶発振子のモデル化

ケーブルのLCを直接測る

伝送路の損失を無視した場合、特性インピーダンス

であるため、直接ケーブルのLとCを計測してやる。


例:50Ωの基準点に75Ωケーブルを単純な変換コネクタを使って接続している。ケーブルインピーダンスは10MHzでの値が規定されていることが多い?ようなので 10MHz で SPAN 0 にし、スミスチャートを表示させている。

  • オープンの場合 139pF
    • オープンはケーブルの芯線とGND間の静電容量を測っている
  • ショートの場合 736nH
    • ショートは芯線とGNDによるインダクタンスを測っている
Math.sqrt(736e-9 / 139e-12) //=> 72.7665034804956

集中定数とみなせる範囲でできるだけ長いケーブルでやるべきとのこと。10MHz の波長は 30m なので、その1/10での 3m 程度で計測するのが正しいらしい。長すぎると集中定数とみなせず、短かすぎると誤差が多くなる。

欠点

  • 基準点に直結できる必要がある
  • 精度が良くないらしい (50Ωから離れたところを計測するので)

λ/8 法

やりかたと例


  1. オープンケーブルを繋ぐ
  2. リアクタンスが0になる最初の周波数をさがす → 28.24MHz
    • スミスチャート的にはX軸と交わるところを探す
  3. 得られた周波数のさらに半分の周波数を計測する → 14.1MHz
  4. リアクタンスを読んで絶対値をみる → -71.6Ω → 71.6Ω (ショートさせたほうが正確らしい?)

欠点

  • 基準点に直結できる必要がある

利点

  • 比較的正確?

原理

伝送路の損失を無視した場合、伝送路の特性インピーダンス と入力インピーダンス には を伝送路の電気長、 を負荷インピーダンスとすると以下のような関係がある

は波数

特に伝送路端がオープン ( ) の場合

ショート( ) の場合

オープンの場合で電気長 の場合、

ということでこのようなケースの場合は入力インピーダンスの虚部が特性インピーダンスと一致する。

伝送速度 のとき周波数 なので、測定したいケーブルの長さを固定とすると周波数をうまく選んであげれば良いことになる。

ところで、電気長が の伝送路(オープン)は共振を起こす、つまりリアクタンスがゼロになる。共振点を探すのは周波数領域では簡単なので、まずこの共振点を探し、さらに周波数を半分にすることで電気長 の条件を達成できる。

TDR

直読できる

欠点

  • 基準点から計測点までのケーブル損失によって誤差がでる
  • 多重反射の影響をうける

利点

  • グラフと現実が一致するのでわかりやすい
  • 余計な操作がいらない

参考文献

  1. トップ
  2. tech
  3. VNA によるケーブル特性インピーダンス測定

  • DC外挿してないけどなんで?
  • どのぐらい誤差がでる?

DC外挿とは?

VNA で負の周波数まで拡張して時間領域変換する場合、DC値も計算対象に含むが、VNA はDC値は測定できないため、推定値をいれてあげる (外挿する) 必要がある。

上の図はローパスモードの処理対象の概念図で、DC〜開始周波数までのデータポイントが抜けていることを説明している。

外挿の方法

  • 周波数ステップの延長上に0Hzがくるように調整
  • DC付近の数点から0Hzを推定

推定は簡単にいえば最低周波数が計測ステップ数と同じでなくてはならないということになる。

50kHz〜1.5GHz で計測するなら、1.5e9 / 50e3 = 30000点を計測ステップにしないといけない。がこれはさすがに計測点が多すぎて時間がかかるので、最低周波数を上げる必要がある。しかし最低周波数を上げてしまうとDC推定が困難になるという問題がある。

反射係数の応答は周波数ドメインでは振動している。周波数ステップが広すぎると、直近 n 個が増加傾向だからといって、次のステップでも同じように増加するとはあまりいえず、推定をはずすことが多くなる。なので、最低周波数は上げたくない。

1パスでの処理を諦めて、最低周波数付近だけを先にサンプリングして処理する方法も考えられる。VNA の下限周波数が 50kHz なら、50kHz、100kHz、150kHz を計測してDC推定し、3つの実測値は捨てる。そして測定開始周波数を最大周波数 / 計測点数にセットして全体をスキャンする。こうすればDC値と連続した計測点(n+1)がつくれる。

外挿の方法は自体は単純に、直近 3 個の増減の平均をとり、最初の点から線を延長する形でやるのがよさそうだ。

            nn = np.mean(np.diff(x[0:3]))
            dc = x[0] - nn
            data[0] = dc

NanoVNA では DC外挿してない?

NanoVNA は組み込みだと 101 点計測。これだけの値でDC値を推定しようとすると、周波数ステップが広すぎてうまくいかない。前述のように2パスで処理するとか工夫がいりそうで、これをやるには若干めんどうな設計変更がいる気がする。

外挿しないというのは最低周波数の値 (50kHzとか) をDC値としてそのまま採用しているということで、IFFT 時の周波数ステップには実質的に誤差が生じていることになる。低い周波数ほど誤差が大きく、全域の平均誤差は25kHz。

どのぐらい誤差がでる?

周波数誤差が時間領域でどのぐらい影響するのかの理論値はどう計算すればいいかよくわからないので、実験的に比較してみる。

  • 最大周波数 1.5GHz
  • 窓関数なし
  • 測定点 500 点
  • 測定対象は50cmぐらいの50Ω同軸ケーブルのS11

DC外挿する場合は IFFT の有効なデータ点数は 1001 点、しない場合は 999点 (測定点の最初をDCとして折り返すので)

DC外挿する

python を使って2パスで処理させてみる。前述のように 50kHz、100kHz、150kHz を計測してDC推定し、3MHz〜1.5GHz を 500 ポイント測定した結果に挿入する。実装は python/nanovna.py を変更する形で行なった。変更点

以下の3つのグラフは上から「DC付近の拡大 (周波数ドメイン)」「DC〜200MHzまで切り取り (周波数ドメイン)」「IFFT して積分したステップ応答(時間ドメイン)」

一番上のグラフにはDC外挿のために参照する3つの測定点(+マーカー)と、外挿されたDCの値(☆マーカー)を表示している。

二番目のグラフはもう少し広い範囲を表示している。このグラフは外挿されたDC値を含む

(ちなみに101点でも別にそれほど変化はない)

ちょっと長いが一応グラフ出す手順を書いておく

        dcExtrapolation = True
        port = 0

        if dcExtrapolation:
            print("start:%d stop:%d points:%d" % (self.frequencies[0], self.frequencies[-1], self.points))
            nv.set_frequencies(int(self.frequencies[-1] / opt.points), opt.stop, opt.points)
            print("start:%d stop:%d points:%d" % (self.frequencies[0], self.frequencies[-1], self.points))

        data = nv.scan()
        x = data[port]
        # window = np.kaiser(len(x) * 2, 6.0)
        # x *= window[len(x):]
        nh = len(x) * 2
        NFFT = 2**(len(str(bin(nh)[2:])))
        print("num: %d, NFFT: %d" % (nh, NFFT))
        data = np.zeros(NFFT, dtype='complex128')

        fig = pl.figure(figsize=(8,10), dpi=100)

        ax1 = fig.add_subplot(3, 1, 1)
        ax1.set_ylim(-1.2, +1.2)
        ax1.set_xlim(0, 200e3)
        ax1.xaxis.set_major_formatter(EngFormatter(unit='Hz'))

        ax2 = fig.add_subplot(3, 1, 2)
        ax2.set_ylim(-1.2, +1.2)
        ax2.set_xlim(0, 200e6)
        ax2.xaxis.set_major_formatter(EngFormatter(unit='Hz'))

        ax3 = fig.add_subplot(3, 1, 3)
        ax3.set_ylim(-1.2, +1.2)
        ax3.set_xlim(0, 20e-9)
        ax3.axhline(y=0, color='grey')
        ax3.xaxis.set_major_formatter(EngFormatter(unit='s'))

        if dcExtrapolation:
            # DC extrapolation

            ## get vna lowest freq data 50kHz 100kHz 150kHz
            _start = self.frequencies[0]
            _end   = self.frequencies[-1]
            _points = self.points
            nv.set_frequencies(50e3, 50e3 * 3, 3)
            print(self.frequencies)
            expdata = np.array(nv.scan()[port])
            ax1.plot(self.frequencies, expdata.real[0:3], marker="+", label="measured real", markersize=10)
            ax1.plot(self.frequencies, expdata.imag[0:3], marker="+", label="measured imag", markersize=10)
            # restore
            nv.set_frequencies(_start, _end, _points)

            ## extrapolate from lowest 3 data points
            di = np.diff(expdata[0:3])
            print(di)
            nn = np.mean(di)
            dc = expdata[0] - nn
            ax1.plot([0], dc.real, marker="*", label="extrapolated real", markersize=10)
            ax1.plot([0], dc.imag, marker="*", label="extrapolated imag", markersize=10)

            # negative freq
            data[-len(x):] = np.conjugate(x)[::-1]
            # positive freq
            data[1:len(x)+1] = x
            ## data[0] is dc term
            data[0] = dc
        else:
            # dc + positive freq
            data[0:len(x)] = x
            # negative freq
            data[-len(x)+1:] = np.conjugate(x)[1:][::-1]

        step = self.frequencies[1] - self.frequencies[0]
        xaxis = np.concatenate([
            np.linspace(0, step * NFFT / 2, int(NFFT / 2)),
            np.linspace(-step * NFFT / 2, 0, int(NFFT / 2), endpoint=False)
            ])

        ax1.plot(xaxis, data.real, marker=".", color="lightgrey", label="real")
        ax1.plot(xaxis, data.imag, marker=".", color="lightgrey", label="imag")
        ax2.plot(xaxis, data.real, marker=".", label="real")
        ax2.plot(xaxis, data.imag, marker=".", label="imag")

        td = np.real(np.fft.ifft(data, NFFT))
        step = td.cumsum()
        print(self.frequencies[1] - self.frequencies[0])
        time = 1 / (self.frequencies[1] - self.frequencies[0])
        t_axis = np.linspace(0, time, NFFT)

        ax3.plot(t_axis, step, label="step response")
        # ax3.plot(t_axis, td,label="impulse response")

        ax1.legend( loc = 'lower right')
        ax2.legend( loc = 'lower right')
        ax3.legend( loc = 'lower right')

DC外挿しない

外挿しないので一番上のグラフには何も描かれていない。

単純に開始周波数を 50kHz として、その値をそのままDC値として利用する。イメージとしては中央のグラフが本来あるべきものより左に少しずつずれている。終了周波数は一緒

なお開始周波数を 500kHz 5MHz 10MHz と段階的に上げていくと以下のように崩れていく。

比較

別々に見ても違いがわからないので、外挿したケースと外挿せず50kHzをDCとみなすものの重ねあわせている。

確かに 50kHz と DC外挿をちゃんとやる場合とで若干差がみえる。が、-1 〜 +1 の範囲で見ている限りはかなり分かりにくい。

雑感

現行の実機の実装に正確なDC外挿を入れるのはけっこうやっかいなのと、実質的な使われかたは絶対的な数値というよりも相対的な変化地点の発見なので、がんばってやる価値があるかはちょっと微妙だなあという印象。何かもっと影響を受けやすいDUTがあれば違うのかも。

参考資料

日本語での文献がほとんどなく、英語でも実際にどうやってDC外挿を行なうべきかの資料はあまりないので結構こまった。VNAのマニュアルやアプリケーションノートのおかげである程度理解できた。

  1. トップ
  2. tech
  3. NanoVNA の時間領域測定 (TDR/TDT) ローパスモードのDC外挿