中華な安定化電源 Wanptek NPS3010W

中国製の廉価な安定化電源買いました。
(今までケンウッドの中古を使ってたのですが壊れたので買い替え。)
WanptekというメーカーのNPS3010Wという製品です。
30V/10A のスイッチング電源で、サイズは7×13×23cm、1.3Kgです。
アマゾンで7199円。アマゾン発送ですぐ届きました。

f:id:licheng:20210207131452j:plain:w400

www.amazon.co.jp

実装

ぱっと見の目視ではヤバそうな実装やリワークは見当たりません。まあまあの品位です。

f:id:licheng:20210207132237j:plain:w400

良いところ

  • コンパクトで軽い
  • 安い
  • FINEツマミがあって電圧調整しやすい
  • まあまあな品位の実装
  • 30V 10Aの出力 (実力は未検証。そこまで期待してない)
  • メーカーが日本語でTwitterYouTubeやっててやや安心感

良くないところ

  • OUTPUTボタンがない
  • OFFしても電圧は急には落ちない
  • 入力115V±10%なので、日本の100Vは少し設計範囲外
  • メーカーさんの日本語がややあやしい (中華ではかなりマシなほう)

未検証なところ

  • リップル・ノイズの程度
  • 電流出力の実力
  • 長時間使用での安定性

その他

不満を言えば以下のようにキリがありませんが、そんな文句を言うような価格帯の商品ではないと思います。

  • 電源プラグが3P(アース付き)で2Pアダプタが付属しない
  • ツマミがエンコーダじゃなくて1回転ボリューム
  • 付属ケーブルはY端子よりバナナの方がよかった
  • 日本語の説明書が無い (←べつに説明書なんか要らんやろ)

あと、鳴きは耳をすまさないと聞こえないレベルで、じゅうぶん静かです。
(※個人の感想です。もうオッサンなので聴覚に自信なし)

注意点

国産(テクシオ、菊水、TDKラムダなど)の安定化電源とは1桁以上も価格が違うので、当然ながら同等の品質と信頼性を期待すべきではありません。趣味で使うぶんにはいいかなと思います。それと、安全に注意して目を離さない運用が前提です。エージング等で通電したまま放置するような運用には適さないと思います。
(※個人の見解です。)

高速逆平方根(fast inverse square root)のアルゴリズム解説

高速逆平方根とは?

高速逆平方根(fast inverse square root)とは、平方根の逆数  \frac{1}{\sqrt x} を高速に計算するアルゴリズムです。平方根の逆数は逆平方根とも呼ばれます。逆平方根はベクトルの正規化などに用いられるので、これを高速に計算できるアルゴリズムには大きなご利益があります。

参照: Fast inverse square root - Wikipedia

C言語のコード

高速逆平方根の関数を示します。0x5F3759DF という謎のマジックナンバーが目を引きます。(後述)

float invsqrt(float x)
{
    // y = 1/sqrt(x) = 2 ^ (-1/2 * log2(x))
    long X, Y;
    float y;
    X = *(long*)&x;
    Y = 0x5F3759DF - (X >> 1); // Magic number!
    y = *(float*)&Y;

    // Newton's method
    const float threehalfs = 1.5F;
    float x2 = x * 0.5F;
    y = y * (threehalfs - (x2 * y * y));   // 1st iteration
//  y = y * (threehalfs - (x2 * y * y));   // 2nd iteration

    return y;
}

検証

1から100までの数について、上記の関数で逆平方根を計算し、標準ライブラリのsqrt関数を用いた計算と比較しました。

【テストコード】

#include <stdio.h>
#include <math.h>

int main(void)
{
    double ave_error = 0;
    double max_error = 0;

    for (double x = 1; x <= 100; x += 1)
    {
        // use sqrt()
        double y = 1 / sqrt(x);

        // fast inverse square root
        float fx = (float)x;
        float fy = invsqrt(fx);

        printf("%f, %f, %f\n", x, y, fy);

        // error
        double error = fabs((fy - y) / y) * 100;
        ave_error += error;
        if (error > max_error) max_error = error;
    }
    ave_error /= 100;

    printf("ave_error = %f, max_error = %f\n", ave_error, max_error);
}

【結果】
f:id:licheng:20210206202714p:plain

グラフがほとんど重なっています。誤差は最大0.175%、平均0.088%ほどでした。このように、わずかな計算量にもかかわらずかなり精度の良いアルゴリズムです。

アルゴリズムの要点

高速逆平方根アルゴリズムの要点は以下の3点です。

  1. 平方根の計算を対数・指数の計算に置き換える
  2. 浮動小数点型の内部表現を利用した対数・指数の近似計算
  3. ニュートン法による収束で精度アップ

[1] 逆平方根の計算を対数・指数の計算に置き換える

対数の性質より


\log_2 \frac{1}{\sqrt x} = -\frac{1}{2} \log_2 x
   … (1)

よって


\frac{1}{\sqrt x} = 2 ^ {-\frac{1}{2} \log_2 x}
      … (2)

これにより、逆平方根の計算を対数・指数の計算に置き換えることができます。

[2] 浮動小数点型の内部表現を利用した対数・指数の近似計算

C言語等で用いられる float型 (32ビット浮動小数点数) の内部表現のバイナリデータ形式に着目すると、対数・指数の簡易な近似計算ができます。

参照: 単精度浮動小数点数 - Wikipedia

float型のバイナリデータは最上位ビットから順に以下にようになっています。

  • 符号ビット: 1ビット
  • 指数部: 8ビット (ただし、+127のバイアスがある。)
  • 仮数部: 23ビット (ただし、[0,1) ではなく [1, 2) の範囲を表す。)

指数部が上位にあることに着目してください。ここで大きなトリックがあります。float型のバイナリデータをlong型(32ビット整数)として解釈すれば、それだけでほぼほぼ  \log_2 x の近似計算になります。逆にlong型のバイナリデータをfloat型として解釈すればほぼほぼ  2 ^ x の近似計算になります。(ただし、指数部のオフセットを考慮すること。また、long型を小数部23ビットの固定小数点数とみなすこと。)

参照: 2^x と log2(x) の高速な近似計算 - 滴了庵日録

これをもう少し詳しく見ていきます。

[2.1] 対数の近似

実数  x を、指数部  e仮数 m \in [0 , 1) を用いて次のように表す。


x = 2 ^ e (1+m)
     … (3)

すると

\log_2 (x) = \log_2 ( 2 ^ e (1+m) )  
= e + \log_2 ( 1+m )

ここで  m \in [0 , 1) なので \log_2 ( 1+m ) \approx m + \sigma

ただし \sigma は近似を調整するパラメータであり、 \sigma \approx 0.0430357 で最適となる。(後述)

以上より

\log_2 (x) \approx e + m + \sigma
     … (4)

[2.2] σの最適値

\sigmaの最適値として、ここでは誤差の絶対値の最大値が最小となる値を考える。
 m \in [0 , 1) の範囲での \log_2 ( 1+m ) - m の最大値の半分の値を \sigma とすればよい。

 e(m) = \log_2 (1+m) - m = \dfrac{\log (1+m)}{\log 2} - m とすると、
 e'(m) = \dfrac{1}{\log 2} \cdot \dfrac{1}{1+m}  - 1

 e'(m) = 0 のとき  m = \dfrac{1}{\log 2} - 1

よって
 \sigma = \dfrac{1}{2} e \left( \dfrac{1}{\log 2} - 1 \right) = 0.043035666028   … (5)

グラフにすると下図のようになる。
f:id:licheng:20210206190147p:plain

[2.3] 整数型での解釈

さて、実数  x のfloat型バイナリデータをlong型として解釈した整数を Xとする。

指数部  E = e + B ただし B = 127 はバイアス、
仮数 M = m \times L ただし L = 2^{23} とすると、(3) (4) より

 
\begin{eqnarray*}
X &=& E L + M\\
 &=& L (e + B + m)\\
 &=& L ( ( e + m + \sigma ) + ( B - \sigma) )\\
 &\approx& L \log_2 (x) + L (B - \sigma)
\end{eqnarray*}

よって

\log_2 (x) \approx \dfrac{ X }{ L } - (B - \sigma)
     … (6)

[2.4] 逆平方根の計算とマジックナンバー0x5F3759DF

 y = ​\frac{1}{\sqrt x} とすると、(1) より


\log_2 (y) = -\frac{1}{2} \log_2 (x)

ここで、 y のfloat型バイナリデータをlong型として解釈した整数を Yとすると、(6) より


\dfrac{ Y }{ L } - (B - \sigma) = -\dfrac{1}{2} \left( \dfrac{ X }{ L } - (B - \sigma) \right)

よって、

Y = \frac{3}{2} L(B - \sigma) -\frac{1}{2} X
     … (7)

ここで、 \frac{3}{2} L(B - \sigma) = 0x5F3759DF が謎のマジックナンバーの正体である。

ただし、(5) で求めた \sigma の値から計算すると 0x5F37BCB6 となり、一般的に知られているマジックナンバー 0x5F3759DF と微妙に食い違います。この理由はよく分かりません。

(7) で求めた  Y をfloat型のバイナリデータとして解釈すれば、 y = ​\frac{1}{\sqrt x} の近似値が求まる。
これはまさに (2) の近似計算である。

[3] ニュートン法による収束で精度アップ

[2]の方法で求めた近似値から、さらに誤差を0に収束させるためにニュートン法を用いる。

誤差が0であれば、 y = ​\frac{1}{\sqrt x} より y^{-2} - x = 0

そこで関数  f(y) = y^{-2} - x を定義し、  f(y) = 0 の解をニュートン法で求める。

まず導関数 f'(y) = -2y^{-3}

よって、 y の近似値  y_nに対し、次の漸化式でより正確な近似値  y_{n+1} が得られる。


\begin{eqnarray*}
y_{n+1} &=& y_n - \dfrac{ f(y_n) }{f'(y_n)}\\
&=& y_n \left( \frac{3}{2} - \frac{1}{2} x y_n^2 \right)
\end{eqnarray*}

[2]の方法で求めた近似値を初期値  y_0 として、この漸化式の計算を繰り返すことでより正確な近似値が得られます。先に示したC言語のコードでは、この漸化式を1回のみ計算しています。(2回目の計算はコメントアウトしています。)

感想

これ思いついたやつ、頭おかしい。
わずかな行数のコードに込められた数学がすごすぎる。

2^x と log2(x) の高速な近似計算

C言語のfloat型のバイナリ表現形式に着目すると、2^x と log2(x) の近似値を高速に計算できます。

float型のバイナリ表現形式

float型は32ビットで、仮数部が23ビット(ケチ表現)、指数部が8ビット、MSBが符号ビットです。

単精度浮動小数点数 - Wikipedia

このように上位ビットが指数を表してることを利用します。

2^xの計算

実質、足し算1回で計算できます。
(ここでは検証のために浮動小数点⇒固定小数点の換算をしています。)

  // y = 2^x (x=-10~10)
  for (float x = -10.0; x <= 10.0; x += 0.25)
  {
    // xの固定小数点表現 (小数部23ビット)
    int32_t fxp_x = x * (float)(1 << 23);
    
    // 整数部に127をオフセット(float型の指数部の仕様のため)
    int32_t tmp = fxp_x + (127 << 23);
    // float型として解釈すれば概ね 2^x の値となる
    float y = *(float*)&tmp;

    printf("%f, %f\n", x, y);
  }

縦軸を対数軸でプロットすると概ね直線になります。

f:id:licheng:20210203200620p:plain

拡大すると細部が周期的に少し歪んでいます。これはfloat型の下位23ビットは指数表現ではないためです。

f:id:licheng:20210203200634p:plain

log2(x)の計算

実質、引き算1回で計算できます。
(ここでは検証のために固定小数点⇒浮動小数点の換算をしています。)

  // y = log2(x) (x=0.001 ~ 1000)
  for (float x = 0.001; x <= 1000; x *= 1.1)
  {
    // int32_t型として解釈
    int32_t tmp = *(int32_t*)&x;
    // 指数部に-127をオフセット(float型の指数部の仕様のため)
    // 概ね log2(x) の固定小数点表現となる (小数部23ビット)
    int32_t fxp_y = tmp - (127 << 23);

    // 浮動小数点に換算
    float y = (float)fxp_y / (float)(1 << 23);

    printf("%f, %f\n", x, y);
  }

横軸を対数軸でプロットすると概ね直線になります。

f:id:licheng:20210203201254p:plain

拡大すると細部が周期的に少し歪んでいます。これはfloat型の下位23ビットは指数表現ではないためです。

f:id:licheng:20210203201305p:plain

高速逆平方根への利用

高速逆平方根(fast inverse square root)つまり 1/√x の高速な計算のために、ここで述べた 2^x と log2(x) の近似計算が利用されています。

Fast inverse square root - Wikipedia

DF11コネクタ

ヒロセのDF11コネクタは、2mmピッチ2列型のコネクタであり、基板対ケーブル、ケーブル対ケーブル、基板対基板のいずれにも対応できるのが特長です。

ただ、部品のバリエーションが多いために少し分かりにくいので以下にまとめました。

コネクタのバリエーション

コネクタ種別 用途 オスメス 型番(x=極数)
圧接ソケット ケーブル メス DF11-xDS-2R26
圧着ソケット ケーブル メス DF11-xDS-2C
レセプタクル
(DIP/SMD)
基板 メス DF11-xDS-2DSA
DF11CZ-xDS-2V
ピンヘッダ
(ストレート/アングル/SMD)
基板 オス DF11-xDP-2DSA
DF11-xDP-2DS
DF11CZ-xDP-2V
中継プラグ ケーブル オス DF11-xDEP-2C
中継アダプタ - オス/オス DF11-xDEP-2A

※ メッキの種別(金/すず)によって型番の末尾にバリエーションあり

圧着端子(コンタクト)

圧着端子種別 オスメス 型番(yy=ケーブルの太さ[AWG])
ソケット用圧着端子 メス DF11-yySC
中継プラグ用圧着端子 オス DF11-EPyyPC

※ メッキの種別(金/すず)やバラ/リールによって型番の末尾にバリエーションあり

使い方

(1) 基板対ケーブル

基板側にピンヘッダ(オス)、ケーブル側に圧着ソケット(メス)を使います。
圧着ソケットにはソケット用圧着端子が必要です。また、圧着ソケットのかわりに圧接ソケットを使うこともできます。ケーブルを一本ずつ剥いてコンタクトにギュッとするのが圧着、ケーブルを剥かずにまとめてガチャっとやるのが圧接です。

(2) ケーブル対ケーブル

片方のケーブルに中継プラグ(オス)、もう片方のケーブルに圧着ソケット(メス)を使います。
中継プラグには中継プラグ用圧着端子が必要です。また、圧着ソケットのかわりに圧接ソケットを使うこともできます。

(3) 基板対基板

片方の基板にピンヘッダ(オス)、もう片方の基板にレセプタクル(メス)を使います。

まとめ
用途 オス メス
基板対ケーブル ピンヘッダ(基板) 圧着ソケット(ケーブル)
ケーブル対ケーブル 中継プラグ 圧着ソケット
基板対基板 ピンヘッダ レセプタクル

名前が分かりにくいので、個人的には「基板用オス」「基板用メス」「ケーブル用オス」「ケーブル用メス」「オス用コンタクト」「メス用コンタクト」などと呼んでます。

メーカーの製品ページ

www.hirose.com

STM32F031のF0,F1の罠

STM32F031のF0,F1ピンは外部クロックのピン(OSC_IN, OSC_OUT)を兼ねている。この切り替えはRCCレジスタのHSEONビットでおこなわれる。リセット値は 0 すなわち外部クロック無効なので、デフォルトではGPIOとして使える。

…のだが、環境によっては、main関数より前に実行されるスタートアップルーチンで勝手にHSEONが 1 にセットされている場合がある。実際には内部クロック動作で使用するにも関わらずである。HSEONが 1 すなわち外部クロック有効の設定では、当然ながら F0,F1ピンはGPIOとして使えない。

その場合は、下記のようにHSEONビットをクリアすればよい。

RCC->CR &= ~((uint32_t)RCC_CR_HSEON); // HSE無効

12月のまとめ

進捗

  • 技術書典10(オンライン)の原稿執筆
  • Arduino系小物ライブラリの開発
  • 3Dプリンタの導入

同人誌

techbookfest.org

所感

今月はまた低調。しかし今年は低調な時もなんとかコンスタントにアウトプットを続けられた。来年も体調が許す限りはアウトプットを絶やさないようにしたい。

三相インバータの黒魔術

DC電源と6つのスイッチング素子からなる三相インバータでACモータを駆動することを考える。

f:id:licheng:20201222142851p:plain

下図上段のような単純な120°位相ずれの正弦波の相電圧(ただし実際にはPWM変調)をインバータが出力すると、モータのコイルにかかる線間電圧は下段のようになる。DC電源電圧の√3/2倍 = 86.6% の振幅しか出せない。(※後述のメモ参照)

f:id:licheng:20201222141029p:plain

これに対し、下図上段のような一見すると珍妙な相電圧を出力すると、線間電圧は下段のような正弦波になり、DC電源電圧の100%の振幅を出すことができる。

f:id:licheng:20201222141209p:plain

これが空間ベクトル変調(SVM)の黒魔術であり、「三次高調波注入 (Third Harmonic Injection)」と呼ばれる。

ソースと計算結果

上記のグラフの計算に用いたソースコード、および計算結果の表とグラフを掲げる。

メモ

f:id:licheng:20210129213513j:plain:w600