2018年6月22日金曜日

アナログ特小周波数表

 ワイドバンドレシーバで特小を聞くときに便利かなーって。
 たまにイベントの手伝いとかすると特小使うけど、レシーバで聞くときにグループとか使われると困るのでメモ。
 普通にトランシーバで聞けよ、という話なのだけど、受信はイヤホンで聞きたいというときに、受信はレシーバで、送信はトランシーバで、みたいな運用がしたかったり。

 デジタルスケルチは送信の頭で出力するから一旦途切れると以降受信できない、みたいな噂があるらしいけど、そんなことはない。TRで送信してる途中でRXの電源を入れてもちゃんとスケルチ開く。


 周波数のソース
 特定小電力トランシーバ周波数表 β - NAGOYA AB449

 トーンスケルチのソース
 トーン周波数一覧(グループ番号)

 デジタルトーンスケルチのソース
 手動で探索


2018年6月11日月曜日

最小構成環境でSingleton

 Singleton パターン - Wikipedia

 組み込みの最小構成な(mallocが無い)環境ではwikipediaの例はうまく動かない。
 関数の中でstaticに宣言するとnewで確保されるようだ。

 解決方法としては、Singletonクラスのprivateでstaticに宣言し、適当な場所に実態を置いておく。
 こうすると、Singletonコンストラクタが呼ばれるのはグローバル変数の初期化時のはずなので、いつ初期化されるかわからない、という事態も避けることができるはず。
 まぁSingletonのコンストラクタにタイミングクリチカルな処理なんて書くな、という話なのだが。

***

 久しぶりにSTM32F1で遊んでる。なぜかUSART bootloader経由でプログラムを書き込むプログラムを作るところから始めてしまった。
 ウチの環境ST-Link(Nucleo割った)が使えないので、USART bootloaderが使えるとF1とかもそのまま使えるので便利。かな。
 STM32F0あたりだとPIC16F88より安い(チップ単体比)ので、ごく小規模なモノなら気兼ねなく使えるかも。

 STBee mini使うの久しぶりすぎてだいぶ手こずった。
 今回はprintfはおろか、USARTといった文字出力系の初期化もしていないので、バイナリサイズは極めて小さいが、デバッグはかなり手間取る。
 バイナリサイズが小さいのは転送速度が遅い(max115.2kbaud+オーバーヘッドがでかい)で相殺されてるので書き込み速度はUSB DFUを使った100k程度のバイナリと体感であまり変わらない。ビルドは早い気がする。

2018年6月6日水曜日

パルス圧縮

 ポカミスで変なバグ仕込んだ挙げ句に一晩熟成されてて全く原因わからず全波形ダンプしたので、ついでにパルス圧縮前と圧縮後の比較。

 全期間の波形

 10msec前後の拡大

 青が入力波形、赤がパルス圧縮後の波形。
 青は12bitADCの信号そのまま、赤はFFT後の値そのまま。横軸の単位はミリ秒。

 条件はパルス幅5msec、計測時間80msec、ディレイ2msec、Fc40kHz、ΔF5kHz、DAC500kSPS、ADC125kSPS、観測時30.3degC / 349.4m/sといった感じ。

 上の図では2msecのオフセットをしていないので、実時間に変換するには2msecを足す必要がある。

 0msecからの大きなピークは直接波を見ている。10msecのピークは天井からの反射波を見てる。
 パルス圧縮すると7msecにピークがある。実時間だと9msecあたりで、実際の天井の位置とおおよそ一致している。

 受信波がかなり乱れてるが、7msecから受信が始まり、パルス幅が5msecなので、12msecで受信が終わっている。心の目で見ると実際にそういうふうに受信されているように見える。
 パルス圧縮後の波形はおよそ0.3msec程度くらいに見える。ということは、実測の圧縮比は5msec/0.3msecで16くらいか。
 計算上は5msec*5kHzでパルス圧縮比25になる。だいぶ特性悪化してる。それでも半分までは落ちてないが。

***

 ゲート幅を狭くすれば意外と短時間で処理が終わる。
 受信素子を2個にすればモノパルス2次元追尾レーダーくらいは作れそうな気もする。うまく作れればサバゲの自動砲台とかも作れそう。
 ただ、探知距離が極めて短く、10m未満くらいが実用的な範囲。エアガンの射程を考えるともう少しレンジがほしい。直径15cmくらいのパラボラが有ればいいかな? パラボラの軸に合わせて送信素子を置いて、少しずらした2個の受信素子を置けば良さそう。

 捜索モードは素子1個を使ってゲート幅を広くし、レンジ内に十分な強度のエコーが有れば捕捉モードに移行して2個の素子で十分に受信できるようにし、その後に追尾モードに移行して2個の素子の強度の差が規定値になるようにする、というような挙動が必要なはず。

 砲台として使う場合は、ある程度の距離に入り、かつ接近率が0以上の場合は射撃を行い、接近率が0を下回った場合は射撃を停止する、という感じか。おそらく接近率が0未満の目標は無視して捜索モードに移行する、という処理になるはず。
 ということは、走って砲台に接近し、捕捉モードに入った時点で後退し捜索モードに移行させ、というサイクルを繰り返せば砲台の直近までは移動できそう。となると、ある程度の距離未満になった場合は接近率にかかわらず射撃する、という処理も必要かも。

 CIWSファランクスだと自前のレーダーで自分の弾丸の弾道を調べてフィードバックさせるけど、さすがに波長8mmの超音波でそれをやるのは無理な気がする。
 数百kHzの素子もあるから、それを使えばBB弾を捜索できるかもしれないけど、Cortex-M4じゃ無理そうだなぁ。

2018年6月2日土曜日

超音波距離計

 超音波距離計は飽きたと言ったな、あれは(ry


 FFTで作った相関器を試してみました。

 DAC800kSPS、ADC50kSPS、パルス幅10.24msec、f40kHz、ΔF5kHz、サンプリング期間8192ポイント、という感じのパラメータです。

 送信は十分に高い周波数で行っていますが、受信は40kHzの信号を50kSPSでサンプリングしています。明らかに元の波形は取れないわけですが、ちゃんと相関が取れています。崩れたなりの波形同士でちゃんと相関処理ができてるってことですかね。

 今まではDACとADCの周波数が同じだったので、DACで送信した波形をADCの相関に使用していました。そのあたりの変更がちょっと面倒でした。

 あとCMSIS DSP LibではFFTが4kポイントまでという制限、それからRAMが足りないという制限により、FFTは1kポイントで実行しています。


 上の図では1.5m先の天井付近に鋭いピークがあります。
 以前は天井で反射し、机で反射し、さらに天井で反射したようなタイミングのエコーがありましたが、今回はそれは見られません。ゴチャゴチャしてるどれかがそれなのかも。
 サンプリング周波数が低いので、そのあたりの悪影響が意外と大きいかもしれません。


 とりあえずパルス幅10.24msecで約140msecのサンプリングが可能でした(運が悪いとmallocでコケますが)。
 140msecだと20m程度の距離に相当しますから、1回のパルスで20m程度の幅のエコーを検出できます。以前は5m程度までだったので、大幅な改善です。とはいえ、これはFFTとかは関係なく、サンプリング周波数が下がったのが大きな要因だと思いますが。


 今回は全期間(140msec程度)をサンプリングした後にまとめて相関処理に入れていますが、なんとなく、計測時間よりも早く相関ができそうな気がします。ということは、ADCバッファの長さはせいぜい2048サンプル分有れば良いわけで、今は8192サンプル分を確保していますから、12KiB程度を開けることができます。
 その分に結果を保存しながらサンプリングしながら、というふうにすれば、かなり長い期間のサンプリングも可能な気がします。
 今の所、すべてのDAC/ADC転送が終わったのはDMAのカウンタをソフトウェアループで見て確認していますが、転送中に相関処理を行うならDMAの割り込み等を駆使する必要があります。そのあたりがかなり複雑になりそうですね。

2018年6月1日金曜日

STM32F4でFFT

 F4でFFTをやってみた。

 テストコード

bool flag(true);
uint8_t is_complex(0);
size_t length(0);
float SPS(0);
float freq(0);

const uint8_t ifft_flag(0);
const uint8_t do_bit_reverse(1);

flag = flag && (4 == sscanf(command,
                            "fft %hhu %u %f %f",
                            &is_complex,
                            &length,
                            &SPS,
                            &freq));

if (flag)
{
    printf("FFT %s %upoint %.1fSPS %.1fHz\n",
            is_complex ? "complex" : "real",
            length,
            SPS,
            freq);
}

if (is_complex)
{
    const arm_cfft_instance_f32 *S(0);

    if (flag)
    { // check length
        switch (length)
        {
        case 16:
            S = &arm_cfft_sR_f32_len16;
            break;
        case 32:
            S = &arm_cfft_sR_f32_len32;
            break;
        case 64:
            S = &arm_cfft_sR_f32_len64;
            break;
        case 128:
            S = &arm_cfft_sR_f32_len128;
            break;
        case 256:
            S = &arm_cfft_sR_f32_len256;
            break;
        case 512:
            S = &arm_cfft_sR_f32_len512;
            break;
        case 1024:
            S = &arm_cfft_sR_f32_len1024;
            break;
        case 2048:
            S = &arm_cfft_sR_f32_len2048;
            break;
        case 4096:
            S = &arm_cfft_sR_f32_len4096;
            break;
        default:
            flag = false;
            printf("length error\n");
            break;
        }
    }

    float *FFT_input(0);

    if (flag)
    { // alloc
        try
        {
            FFT_input = new float32_t[length * 2]();
        }
        catch (std::bad_alloc)
        {
            flag = false;
            printf("bad alloc\n");
        }
    }

    if (flag)
    { //generate wave
        for (size_t i(0); i < length; i++)
        {
            const float phase(i * pi * 2 / SPS * freq);
            FFT_input[i * 2 + 0] = cosf(phase);
            FFT_input[i * 2 + 1] = sinf(phase);
        }
    }

    float FFT_process_seconds(0);

    if (flag)
    { // FFT
        const uint32_t start(HAL_GetTick());
        arm_cfft_f32(S, FFT_input, ifft_flag, do_bit_reverse);
        const uint32_t stop(HAL_GetTick());

        FFT_process_seconds = (stop - start) * 0.001f;
    }

    if (flag)
    { // dump result
        printf(" %.3fsec\n\n re im\n", FFT_process_seconds);

        for (size_t i(0); i < length; i++)
        {
            const int j(i - length / 2);
            const int k(i < length / 2 ? (i + length / 2) : (i - length / 2));
            printf("%f %f %f\n",
                    j * SPS / length,
                    FFT_input[k * 2 + 0],
                    FFT_input[k * 2 + 1]);
        }
    }

    delete[] FFT_input;
    FFT_input = 0;
}
else
{
    arm_rfft_fast_instance_f32 S;

    if (flag)
    { // check length and initialize
        flag = (ARM_MATH_SUCCESS == arm_rfft_fast_init_f32(&S, length));

        if (!flag)
        {
            printf("init error\n");
        }
    }

    float *FFT_input(0);
    float *FFT_output(0);

    if (flag)
    { // alloc
        try
        {
            FFT_input = new float32_t[length]();
            FFT_output = new float32_t[length]();
        }
        catch (std::bad_alloc)
        {
            flag = false;
            printf("bad alloc\n");
        }
    }

    if (flag)
    { //generate wave
        for (size_t i(0); i < length; i++)
        {
            const float phase(i * pi * 2 / SPS * freq);
            FFT_input[i] = cosf(phase);
        }
    }

    float FFT_process_seconds(0);

    if (flag)
    { // FFT
        arm_rfft_fast_f32(&S, FFT_input, FFT_output, ifft_flag);
    }

    if (flag)
    { // dump result
        printf("%.3fsec\n\n re\n", FFT_process_seconds);

        for (size_t i(0); i < length; i++)
        {
            printf("%f %f\n",
                    i * SPS / length / 2,
                    FFT_output[i]);
        }
    }

    delete[] FFT_input;
    FFT_input = 0;

    delete[] FFT_output;
    FFT_output = 0;
}

 リンカでDrivers/CMSIS/Lib/GCC/libarm_cortexM4lf_math.aを読み込む必要がある。

 ARM_MATH_CM4(コアに合わせて選択)をdefine定義した後、arm_math.hとarm_const_structs.hをインクルードする。あとnewのstd::bad_allocをcatchするのに<new>もインクルードする必要がある。


 CFFTは複素数、RFFTは実数の処理で、それぞれ呼び方が違う。また引数のとり方も違い、CFFTはinputとoutputを同じ配列で行う。一方、RFFTはinputとoutputは別の配列を使う。
 CFFTは負の周波数も解析できる。一方、RFFTは負の周波数は扱えないが、CFFTの分解能の2倍くらいあると思う。1024ポイントでSPS1kHzとした場合、CFFTは1024ポイントで-0.5kHzから+0.5kHzの範囲を取る。RFFTは1024ポイントで0Hzから0.5kHzの範囲を取る。

 ちゃんと試してないけど、CFFTは16ポイントから4kポイントまで、RFFTは32ポイントから4kポイントまで解析できる。

 CFFT/RFFTを行う上記コードを含めるとバイナリサイズは256kくらいになった。この部分をコメントアウトすると128k位になる。FFT解析を行うには128kほどのFlashが必要になる。もちろん、FFTを行う上でメモリ領域も必要になる。


 const char *const commandに文字列を入れておけば、それを解析する。主にUART等からコマンドを受け取って実行する事を想定。もちろんsprintfで作ってもいいけど、そんな事するくらいなら直接呼べという話で。

 "fft 1 4096 100 -10" というコマンドを実行すると、複素数で100SPS/-10Hzの波形を生成しFFT解析する。
 "fft 0 4096 100 30" というコマンドを実行すると、実部のみで100SPS/30Hzの波形を生成しFFT解析する。
 それぞれの結果は以下の通り。



 CFFTは左端が-50Hz、右端が+50Hz、RFFTは左端が0Hz、右端が+50Hzになっている。どちらも結果は4096ポイント得られるので、RFFTのほうが倍の分解能になる。

 4096ポイントの場合、168MHzのCortex-M4FでCFFTが4msec未満、RFFTが1msec未満だった。CFFTは若干の時間がかかるが、RFFTは恐ろしく早い。
ただ、どちらも4kポイントだと32KiBのRAMが必要になる。自由に使えるヒープ領域は100KiB程度なので、あまり大規模なものを作ろうとするとちょっと心もとない。

***

 とりあえずFFTが動くようになったので、パルス圧縮の相関処理とかもできるわけだが、時間的・空間的な余裕がかなり少なそう。
 超音波距離計もちょっと飽きてきたし、気分転換に別のヤツで遊ぼうかな。
 FFT/IFFTが動くならOFDM変調とかできる。