2018年2月27日火曜日

STM32F4のCAN

 気まぐれにSTM32F4のCANを試してみたくなったので、簡単に動作チェック。
 STM32のCANはボーレートの指定がちょっと面倒。USARTのように直接指定することができない。そしてCAN独特のパラメータにより、ボーレートの調整が面倒。

 ペリフェラルの初期設定はあまりいじるところはないが、今回はループバックモードにしている。これにより、CANドライバ(シングルエンドと平衡接続の変換を行う、典型的に8pinのIC)が無くても動作できる。またCANのポートをフロートにした場合、RXがドミナントに固定されて問題が発生する(ISRが呼ばれ続ける?)場合があるので、CAN_RXをプルアップに変更している。
 プリスケーラは8、TQ1が15、TQ2が5で250kbaudに設定している。


 まずフィルタの初期設定を行う。
 今回はフィルタの動作確認はしないので、とりあえず受信したデータをすべて受け取るというモード。

CAN_FilterConfTypeDef filter = { 0 };

filter.FilterFIFOAssignment = CAN_FILTER_FIFO0;
filter.FilterNumber = 0;
filter.FilterMode = CAN_FILTERMODE_IDMASK;
filter.FilterScale = CAN_FILTERSCALE_32BIT;
filter.FilterActivation = ENABLE;

HAL_CAN_ConfigFilter(&hcan1, &filter);

 フィルタモードがマスクの場合、フィルタが1のビットが一致したIdだけを受信する。今回の設定ではすべてのビットが0なので、すべてのビットに対してチェックを行わず、結果的にすべてのIdを受信することになる。
 STM32F1でCANを試した時に、フィルタの挙動がよくわからなくて、結局すべてを受信するようにしてソフトウェアでフィルタリングしたような気がする。。。


 CANの送信と受信の処理。
 とりあえずUSARTからchar str[]バッファに1行分読み込まれ、それを解析して処理する、という流れになっている。
 "can (arg)"コマンドでメッセージを送信し、"canrx[0-1]"でFIFO0かFIFO1からメッセージを受信する。

// 追記(2018/03/02)以下のコードにはバグが有るので最後の追記を読むこと

if (!strncmp(str, "can ", 4))
{
    int flag = 1;

    uint32_t id = 0;
    char id_type = 'S';
    char MSG_type = 'd';
    uint32_t data_length = 0;
    uint8_t data[8];

    int parse_count = 0;

    if (flag)
    {
        parse_count = sscanf(str + 4, "%lu %c %c %lu %hhx %hhx %hhx %hhx %hhx %hhx %hhx %hhx", &id, &id_type, &MSG_type,
                                &data_length, &data[0], &data[1], &data[2], &data[3], &data[4], &data[5], &data[6], &data[7]);
    }

    uint32_t IDE = CAN_ID_STD;
    uint32_t RTR = CAN_RTR_DATA;

    if (flag)
    {
        // parse argument
        switch (id_type)
        {
        case 's':
        case 'S':
            IDE = CAN_ID_STD;
            break;

        case 'e':
        case 'E':
            IDE = CAN_ID_EXT;
            break;

        default:
            flag = 0;
            break;
        }

        switch (MSG_type)
        {
        case 'd':
        case 'D':
            RTR = CAN_RTR_DATA;
            break;

        case 'r':
        case 'R':
            RTR = CAN_RTR_REMOTE;
            break;

        default:
            flag = 0;
            break;
        }

        flag = flag && (parse_count == 4 + (RTR ? CAN_RTR_DATA == (int)data_length : 0));
        flag = flag && IS_CAN_DLC(data_length);
        flag = flag && IDE == CAN_ID_STD ? IS_CAN_STDID(id) : IS_CAN_EXTID(id);
    }

    CanTxMsgTypeDef TX_MSG = { 0 };

    if (flag)
    {
        // set TX_MSG
        if (IDE == CAN_ID_STD)
        {
            TX_MSG.StdId = id;
        }
        else
        {
            TX_MSG.ExtId = id;
        }

        TX_MSG.IDE = IDE;
        TX_MSG.RTR = RTR;
        TX_MSG.DLC = data_length;

        for (uint32_t i = 0; i < data_length; i++)
        {
            TX_MSG.Data[i] = data[i];
        }
    }

    if (flag)
    {
        // HAL_CAN_Transmit
        hcan1.pTxMsg = &TX_MSG;
        HAL_StatusTypeDef status = HAL_CAN_Transmit(&hcan1, 100);
        hcan1.pTxMsg  = 0;

        flag = status == HAL_OK;
    }

    printf("%s\n", flag ? "ok" : "error");
}

if (!strncmp(str, "canrx", 5))
{
    int flag = 1;

    int FIFO_index = 0;

    if (flag)
    {
        flag = 1 == sscanf(str + 5, "%d", &FIFO_index);
    }

    uint8_t FIFO_number = CAN_FIFO0;
    CanRxMsgTypeDef RX_MSG = { 0 };
    CAN_HandleTypeDef *CAN_handle = &hcan1;

    if (flag)
    {
        switch (FIFO_index)
        {
        case 0:
            FIFO_number = CAN_FIFO0;
            CAN_handle->pRxMsg = &RX_MSG;
            break;

        case 1:
            FIFO_number = CAN_FIFO1;
            CAN_handle->pRx1Msg = &RX_MSG;
            break;

        default:
            flag = 0;
            break;
        }
    }

    if (flag)
    {
        HAL_StatusTypeDef status = HAL_CAN_Receive(CAN_handle, FIFO_number, 10);

        flag = status == HAL_OK;
    }

    switch (FIFO_index)
    {
    case 0:
        CAN_handle->pRxMsg = 0;
        break;

    case 1:
        CAN_handle->pRx1Msg = 0;
        break;
    }

    printf("%s\n", flag ? "ok" : "error");

    if (flag)
    {
        printf("    ID: %0*lX (%s)\n", RX_MSG.IDE == CAN_ID_STD ? 3 : 8, RX_MSG.IDE == CAN_ID_STD ? RX_MSG.StdId : RX_MSG.ExtId,
                RX_MSG.IDE == CAN_ID_STD ? "STD" : "EXT");
        printf("   RTR: %s\n", RX_MSG.RTR == CAN_RTR_DATA ? "DATA" : "REMOTE");
        printf("   DLC: %lu\n", RX_MSG.DLC);

        printf("  DATA: ");

        if (RX_MSG.RTR == CAN_RTR_REMOTE)
        {
            printf("(null)");
        }
        else
        {
            for (uint32_t i = 0; i < RX_MSG.DLC; i++)
            {
                printf("%02hhX ", RX_MSG.Data[i]);
            }
        }

        printf("\n");

        printf("   FMI: %lu\n", RX_MSG.FMI);
        printf("  FIFO: %lu\n", RX_MSG.FIFONumber);
    }
}

/* コードフォーマットにAstyleを使ってるけど、これってブロック開始行にコメントを配置できないのね。改行されてしまうとブロックを畳んだ時にコメントが表示されない。。。 */


 試しに
canrx0
can 1 s d 8 12 23 34 45 56 67 78 89
can 1 e r 4
canrx0
canrx0
canrx0
というコマンドを送ると、
"canrx0"
error
"can 1 s d 8 12 23 34 45 56 67 78 89"
ok
"can 1 e r 4"
ok
"canrx0"
ok
    ID: 001 (STD)
   RTR: DATA
   DLC: 8
  DATA: 12 23 34 45 56 67 78 89
   FMI: 0
  FIFO: 0
"canrx0"
ok
    ID: 00000001 (EXT)
   RTR: REMOTE
   DLC: 4
  DATA: (null)
   FMI: 0
  FIFO: 0
"canrx0"
error
という結果が得られる。

 コマンドの内容は、最初に1回受信を行い、次にStd DATAを送信、続いてExt REMOTEを送信し、最後に3回受信を行う、という感じ。
 最初に受信を行った最はFIFOにデータが無いので、エラーとなる。
 データを送る際はokかerrorが表示される。
 最後に受信を行った際は、Std DATAとExt REMOTEが表示され、3回目の受信はデータが無いのでerrorとなる。

 HAL_CAN_Receiveは内部でFIFOの高さを確認しているので、いきなり呼んでも無意味なデータが取り出されてしまう、という事はない。


 この時、CAN TXは以下のような感じになっている。

 今回はマイコン1個で試しているので、誰もACKを返していない。
 ZEROPLUSのパケット表示は、なんとなく見づらい気がする。CANのプロトコルに忠実に実装しているんだろうが、IDが29bitひとつづきじゃなかったりとか、その辺が、あくまでロジックアナライザなんだな、という感じ。

 本来、だれもACKを返さない場合はずーっとデータを送信し続けるはずなんだが、そうはなっていない。ループバックモードではACKビットをサンプリングしないそうなので、その関係かも。



 // せっかくCANが簡単に扱えそうだし、何か良い使い道ないかなー。3バイトのアドレッシング+8バイトのデータで極めて信頼性が要求される用途かぁ。何か有るかなぁ。。


追記:2018/03/02
 上記のコード、CANの送信コマンドにバグが有る。一部を以下のように変更すること。

flag = flag && (parse_count == 4 + (RTR == CAN_RTR_DATA ? (int)data_length : 0));
flag = flag && (IS_CAN_DLC(data_length));
flag = flag && (IDE == CAN_ID_STD ? IS_CAN_STDID(id) : IS_CAN_EXTID(id));

 バグの問題としては、&&の優先度を見誤っていた点、三項演算子を間違っていた点、の2つ。
 // どーしてCには論理演算代入演算子が無いのか

2018年2月26日月曜日

コヒーレントドップラー検波

 正しい名前は何ていうのか知らん。

 送信周波数と検波の基準周波数が完全に一致した場合にドップラーを検出する方法。






 検波の基準周波数を1Hzとして、上からそれぞれ受信周波数が1Hz、0.9Hz、1.1Hz、1.2Hzの4種類。横軸はフルスケール10秒。
 受信した信号(という想定の正弦波)がRX、基準周波数の正弦波(sin)と余弦波(cos)があって、*sinはRX*sin、*cosはRX*cosの形。ave sinとave cosは*sinと*cosを基準周波数の180度分のサンプル数で平均化したもの。atan2はave sinとave cosのarc tanを計算したもの。
 ave sin、ave cos、atan2の最後の方の波形が乱れているのは、平均化に必要なサンプル数が足りないため。

 1Hz(受信周波数=基準周波数/ドップラーシフトなし)の場合、atan2の値は時間軸で常に同じ値となる。0.9Hz(青方偏移)の場合は右肩上がりのグラフで、便宜的に時計回りと表現する。1.1Hz(赤方偏移)の場合は反時計回りとなる。
 ある程度前のサンプルのatan2と現在のatan2を比較し、時計回りに移動していれば目標が接近中、反時計回りに移動していれば目標は遠ざかっている、と判断することができる。
 1.1Hzと1.2Hzを比較した場合、よりシフトが大きい1.2Hzの方がatan2の変化量が大きいことがわかる。
 0.9Hz、1.1Hzの場合、atan2は10秒で1周している。1.2Hzの場合は5秒で1周している。このことから、ある時間でのatan2の変化量をn回転とした場合、その逆数がドップラーシフトの周波数になることがわかる。基準周波数は既知だから、シフト量がわかれば目標との相対速度を計算できる。


 この方法で低速の目標を見つける場合、十分に長い期間のサンプルが必要になる。例えば1GHzのレーダーの場合、目標が1m/sで移動すると3.3Hz程度のシフトが発生する。この場合、1秒間のサンプルを見ると、atan2の値が3.3回転程度することになる。実際には10マイクロ秒未満のパルスで計測するだろうから、とてつもなく小さな回転量しか得られない。たぶんノイズとの戦いになる。


 この復調方式は、単純では有るが実際に使うにはかなり大変そうな気がする。特に短いパルスで十分な精度が得られるのか、という点が疑問。あとECMに弱そう。
 でも結局どの方式を使うにしろ、微量のシフトを検出しないといけないことに変わりはないから、あんまり問題にはならないのかも。

 超音波でもドップラーシフトは計測できるだろうけど、常に同じ速度で移動するターゲットを作るのが大変そう。

2018年2月21日水曜日

オフセットパラボラ


 自分自身の妨害が結構大きそうな気がするので、オフセットパラボラにしてみました。
 焦点距離10cm、開口20cmで、この数値だけを見ると前回と同様です。



 画像は障害物の有無で撮った状態です。以前のピクチャとは設置場所・画角が異なります。

 感覚的には、前回とそんなに変わらないかなぁ、という気がします。比較したわけじゃないですが。

 下の画像、赤矢印が本棚、緑矢印がパイプベッドのフレームかな、という気がします。本棚までの距離は1.5mですが、1.8m付近に見えています。焦点距離が10cmなので、パラボラで20cm分距離が伸びますから、実際には1.7mの距離に見えるはずです。ということは、大体そんなもんかな、という距離感です。

 本棚からフレームまでの距離はおよそ20cmで、十分に見分けられる距離なので、方位分解能は15cm/1.5m程度、約6度程度かな、という感じです。


***

 エコー反射の長さは10cmくらいでしょうか。この距離は0.6msecくらいに相当します。
 パルス条件はパルス幅(PW)10msec/ΔF4kHzなので、パルス圧縮比(PCR)40になります。ということで、圧縮後のパルス幅は0.25msecになります。が、実際は送受信機の特性が悪いので、もっと幅の広いパルス幅になるはずです。
 エコー0.6msecというのは、0.25msecの2.4倍ですから、多少特性が悪化したとすれば、これくらいの量になるはずです。

 距離分解能を改善するには、PCRを大きくすれば、圧縮後のパルス幅が短くなります。PCRはPWとΔFの積なので、PWかΔFを大きくすればPCRが大きくなります。とはいえ、PWを2倍にすると、PCRも2倍になりますが、元のパルス幅が2倍ですから、最終的に得られるエコー幅に変化はありません。
 ということで、距離分解能を改善するにはΔFを大きくする、という選択肢になります。一方で、送信機や受信機の帯域幅の問題が有るので、無闇矢鱈とΔFを大きくすればいい、という事でもありません。
 超音波の場合、ΔFは3-5kHz程度かな、と思います。ΔFの制限がある以上、圧縮後のパルス幅を大きく改善することはできません。リニアチャープを使った場合、距離分解能は10cm程度が限界のようです。パルス圧縮を行わない場合、例えば0.5msecの超音波パルスを使う場合は距離分解能10cm程度で、パルス圧縮を使っても使わなくても得られる距離分解能に変わりはありません。ただし、パルス圧縮を使った場合はより遠くの目標を検出できる、という利点があります。



 この図はパルス幅を変えて撮影したものです。2msecと10msec、25msecの3条件です。以前の画像はすべて10msecで撮影されています。
 基本的に、距離分解能に違いは見られません。ただし、方位分解能はパルス幅が長いほうが分解能が高いような傾向が見られる気がします。

 また、画像化の際はAGCで信号の強さを正規化していますが、実際にはパルス幅が長い方が大幅に信号が強くなります。
 エコーの強さは送信出力と送信時間の積に比例します。一方、目標が大きいほどエコーが大きく、小さいほどエコーが小さくなります。PWを大きくした場合、エコー幅に影響はありませんが、送信エネルギーが増える分、小さい目標からのエコーも強く受信できるようになります。


 ということで、パルス圧縮を使う場合、その他の条件を無視すれば、できるだけ長い時間、できるだけ広い周波数帯で送信するほど高性能になります。また、送信周波数を広げるには、中心周波が高いほど有利になります。


 なお、これはFMチャープの話で、符号拡散を使った場合はまた別の話になります。
 あと、いろいろ資料を見てるとなんとなく解釈が怪しい気がしてきたので、大幅に間違っている可能性もあります。あんまり信用しないよーに。

2018年2月20日火曜日

パラボラ


 AIM-120の時に作ったスクリプトを流用して、放物線リフレクタを厚紙で作ってみました。補強が一切入ってないので、自重でかなりたわみます。とりあえず下にスペーサーをおいて、極端に沈み込まないようにしています。




緑はリフレクタなし、紺はリフレクタありのときのスコープです。おおよそ同じ範囲を見ています。

 リフレクタ無しでは1つの目標が広い範囲に広がっていますが、リフレクタ有りでは狭い範囲に集中しています。正直、たかが20cmの放物線リフレクタでここまで効果があるとは思っていませんでした。



 ついでに、物体の有無のサンプルです。上は物体あり、下は物体なしで、赤丸の部分にエコーの違いが見られます。



 とりあえず、しばらくは地味なバグ取りとか細かい修正が続きそうです。
 何に使うか決めてないので、飽きたらその時点でやめると思います。元々パルス圧縮を試してみたい、という理由でしたから、すでにある程度の目標は達成しているわけですが。

PPI


 超音波距離計から寄り道して、PWM Outを追加してアジマススキャンをできるようにしました。PC側でそれを表示するソフトウェアを作って、PPIスコープっぽい表示にしてみました。イメージはパトリオットミサイルのレーダースクリーンです。ま、だいぶ違いますが。

 上の図はおよそ4mのレンジで表示しています。なんとなく部屋の配置と同じように見える気がしますが、実際の距離と3割くらい違うので、何か間違ってると思います。

 開口の指向性がかなり鈍いので、小さい目標でもかなり広い範囲に写り込んでしまっています。


 Aスコープ風、Bスコープ風、PPIスコープ風、と、とりあえずエポックメイキングなタイプはおおよそ作った気がします。あとは目標の捜索/追尾や、複数の超音波レーダーからのトラッキングデータをデータリンクに流して…とかでしょうか。

 前述したとおり、指向性がかなり鈍く、方位分解能がかなり悪いです。目標の捜索とかには使えないと思います。このあたりは素子を並列に並べて指向性を作ったり、皿をかぶせて指向性を作ったり、といったことができると思います。
 音波の40kHzは電磁波の35GHzに相当する波長です。衛星放送は12GHz程度ですから、衛星アンテナより小さい大きさで、同じくらいの指向性を得られます。かなり小さい反射器でも高い利得が得られそうな気がします。10cmくらいのディッシュでも十分な性能が出そうです。高精度な放物面をどうやって作るか、という問題はありますが、このくらいの大きさなら3Dプリントを外注してもそんなに高くないかもしれません。放物線のモデルを作るのはそれはそれで大変そうですが。あと、皿が2素子分必要というのもちょっと面倒。


 とりあえず、アジマススキャンができるようになったよ、という報告まで。

2018年2月18日日曜日

 前回から変化したところといえば、温度計を追加した事と、アナログ値の出力回数を10分の1に減らしたことでしょうか。
 温度計はADT7420を使いました。CubeでI2Cの初期化を追加して、レジスタを2バイト読むだけで温度を計測できます。恐ろしく簡単です。
 1-Wireの温度計の代わりにADT7420を使いました。ストリナのADT7420は時定数を下げるためか、0.1mmのリジット基板に実装されていて、ちょっと面倒な感じです。あと、昔I2Cでかなり苦労をしていて、I2Cセンサは嫌だなぁと思っていました。でも1-Wireで面倒なドライバを自分で実装することを考えると、それらのデメリットを補って余りあるかな、という気がします。



 広い部屋に行く用事が有ったので、ついでに超音波距離計の動作テストもやってきました。リファレンスの距離計が無いので正確性はわかりませんが、とりあえず約12m先からのピークが見えます。とはいえ、相関値はかなり低いですが。
 壁からの反射なので、これ以上大きなピークは出ないと思いますが、考えようによっては、大面積からの反射なので、反射位置によって様々な位相が含まれます。それによって相関値が低くなってしまっている、と考えることもできます。

 あと、距離分解能は思ったより低そうです。


 現在、相関器は16bit*16bitの乗算器で14bit*8bitの計算を行っていますが、相関処理だけならもっとビット数が低くても良さそうな気がします。ARMのSIMDの特性上、16bit*16bitより幅の狭い乗算器はありませんが。
 とはいえ、現状の方法よりは性能の良い相関器は色々な方式が有るはずで、もうちょっと改善の余地があります。

 他にも、受信段のオペアンプのゲインを増やして長距離からの信号を増幅する、と言った手段もあります。近距離からの信号は飽和してしまいますが、相関器はアナログ値の上下がクリップされていても動作しますし、近距離に特化したいならDACの出力を小さくすれば送信エネルギーを下げ、受信が飽和しないようにもできます。
 このあたりも一度考え直して見る必要があります。
 オペアンプの増幅率は3m程度の反射で飽和しないように、という狭い部屋の中で決めた値なので、実際にはもっと増幅率を稼いでも問題ないのかもしれません。ただ、すでにユニ基板に乗せてしまったので、今更リード抵抗を交換するのも面倒だなぁ、と思っています。プリント基板作りたい。。。


 またしばらくは狭い部屋でテストを繰り返すことになります。どうなることやら。

2018年2月16日金曜日

測距


マイコン内でLPFを通してピーク検出を行う、というところまで動きました。とりあえず横軸をExcelで音速から距離に計算してグラフ化しています。

 上のグラフはゲート全域のおよそ12m分、下のグラフは3mまでを拡大したものです。

 試しに130cmくらい離れた場所に30cmくらいの間隔でエアダスターの缶を置いてみました。1.35m、1.47m、1.64m、1.86mあたりに山があります。
 おそらく1.35mと1.64mがダスター缶じゃないかな、と思います。間隔は0.31mですから、缶の間隔と一致します。
 その他の山は、1.47mはダスター缶を置いた台の反射だと思います。その間も全体的に高いのは、台の壁面が見えているため、そこからの反射が有るのではないかな、と思います。1.86mは何が反射しているのかわかりません。壁にしては近いし、他のものは空のダンボールとかどれも有り得そうな感じです。


 距離分解能は10cmくらいかな、という感じでしょうか。LPFのCOFをもう少し上げれば距離分解能が稼げるかもしれませんが、それでも5cmくらいが限界だと思います。

 もっとも、パルス幅は10msecなので、パルス圧縮を行わなければ距離分解能は1.7mまで悪化してしまうので、15倍くらい性能が改善しているわけですが。
 パルス圧縮比は40なので、理論値で行けば4.25cm程度になるはずです。調整次第では、理論値に近い性能まで出るかもしれません。


 今のところ、計測を開始してからUARTの出力が終わるまでは20秒くらいかかっています。しかし、UARTを吐くforの中では計算は行っておらず、計測を開始してからUARTの出力が始まるまでは体感で1秒もありませんから、そのほとんどの時間をUARTで食っていることになります。
 データ数が1万4千行あり、1行あたりおよそ20文字近くのデータが有るので、全体では280kbyteくらいを転送していることになります。UARTの転送速度は12kbyte/secなので、280/12で23.33...秒が必要です。実際にはもう少し文字数が少ないので、ほぼ実測値と一致しています。
 ということで、現在でも115.2kbaudのUARTをフルに使っていることになります。また相関等の計算は0.5秒程度(デバッグ出力による)で、ピーク値の検出はほぼ無視できる程度であることがわかります。
 明らかにUARTに流すデータが多すぎるので、このあたりは気にならない程度までデータを飛ばすようにすれば、苛つかない程度には早くなると思います。


 とりあえず超音波距離計として必要な機能の大半は実装が終わりました。次の大きな目標としては温度計を動作させることでしょうか。
 パルス圧縮がおおよそ理論通りに動くようになってきて、そろそろ超音波距離計も飽きてきました。

メモ:undefined reference to `operator new(unsigned int)'

 undefined reference to `operator new(unsigned int)'
 undefined reference to `operator delete(void*)'
 というコンパイラエラーが出る場合、makefileのLDのgccをg++にすればok。

 ちょっと前にマイコンでC++を使おうと思ったことが有るんだけど、newを使おうとすると上記エラーが出て、結局newは使わずmallocを使っていた。
 コンパイラはg++に変えていたけど、リンカがgccのままだった、というオチ。


 これで動的メモリ確保がC++らしく書けるようになった。
 コンストラクタ/デストラクタが不要ならmallocで良いんだけど、mallocは、それはそれで危険なコードなので、使わないならそれに越したことはない。

2018年2月15日木曜日

測距


 6mくらいの距離で計ってみました。38msecあたりに強いピークがあり、これが壁からの反射波だと思います。途中のピークでも、おおよそその位置にモノがあるので、ちゃんと計測できているようです。

 距離が伸びでも、相関値に大きな影響は無いようです。この減り方を伸ばすと、10mくらいならちゃんとピークが出そうです。それ以上の距離はちょっと厳しい気がします。もうちょっと増幅率稼いでおけばよかったなぁ。

 計測幅は80msec、相関の計算には440msec程度です。
 最終的に、SIMDはスカラ演算と比べて2倍の処理速度になりました(forのオーバーヘッド含まず/命令数で計算)。32bitコアで16bit2組のベクトル演算ですから、ちょうど2倍になります。
 SIMDはコンパイラ最適化が少し弱くなるような気がします(インラインアセンブリと同じ書き方ですから、コンパイラ最適化が行われない?)。SIMDを使う場合は、可能な限りSIMD周りを一つの関数にまとめ、それ以外の処理は書かない、と言った工夫が必要なようです。これは関数を呼び出すことにより、レジスタをスタックに退避して、コンパイラが使用可能なレジスタを探すのを助ける、といった動きになりそうです。


 とりあえずAスコープ風の表示でいいなら、今の段階でも十分に使えますが、距離計として使うならピークを検出する処理が必要になります。次はこれを作る事になりそうです。

2018年2月14日水曜日

超音波距離計


 とりあえずユニ基板に実装しました。といってもハンダ付け作業は数日前に行っており、ソフトウェアで結構つまずいていました。

 やっとDACとADCが動くようになったので報告に参った次第。


 STBeeF4miniのボードの下にオペアンプが4回路分、DIP8が2個入ってします。他に抵抗が18本入っています。抵抗は古い1/4Wサイズ(現行の秋月1/2Wサイズ)しか手元になく、寝せると実装面積が足りなくなるので、すべて立てて実装しています。あとはパスコンにチップコンが数個ですが、これはほとんど面積を食いません。



 とりあえずリニアチャープで動くようになったので、机から天井に向けて測距してみました。机から天井まではおよそ160cmです。

 横軸が時間(msec)、縦軸は青(左)がADCの電圧、赤(右)が相関値で、相関値は32bitで計算して、16bitに圧縮して配列に保存し、計算が終わってからまとめてUARTで吐いています。
 一番最初のピークは直接波によるものと思われます。次の9msecあたりが天井からの反射波で、9msec*音速/2は約1.5mなので、実際の距離と近いです(差は温度を計算に含めていないためと思われます)。
 18msecあたりや27msec、36msecあたりにも少しずつピークが有ります。たぶん天井で反射し、それが机で反射し、また天井で反射し、ということだと思いますが、2往復しても十分な相関が有って、目で見るなら4往復でも見える、というのはちょっと驚きです。36msecの部分は6m相当の距離ですが、間で7回反射し、机は乱反射でかなり衰退しているでしょうから、実際の距離ならもっと強くピークが出るはずです。
 最初の20msecは振り切れていますが、これは想定通りです。ブレッドボードで試した時に、オペアンプからの出力が振り切れていても問題なく相関が取れることがわかったので、固定倍率で基板を作りました(適当なボリュームが手持ちになかった、という理由もありますが)。


 今回はマイコンの中で相関の計算を行いましたが、200kspsでパルス幅10msec、受信期間50msecの場合、相関の計算に1100msec以上かかりました。今回はforで回していますが、例えば200組をべた書きでループ回数を減らす、といった、オーバーヘッドを減らすような最適化を行った場合、多少の性能改善が見込めると思います。数割増しから数倍程度に高速化できると思いますが、それでも400-700msec程度です。
 なお、相関の計算は整数で行い、FPUは使用していません。今のところ27.5bit+符号で収まっているので、32bitでも余裕があります。


 ゲート幅5mで500msecとして、ちょっとした測距を行うなら1.5Hz程度でも使えるかもしれませんが、レーダーとして使うのはかなり厳しそうです。
 パルス幅を短くすれば計算量は大幅に減りますが、最大距離も短くなります。

 とりあえず、相関処理の最適化、それと相関結果からピークを拾い出す処理が必要です。あとは温度センサを取り付ければ、温度から音速を計算し、ピーク位置から距離を求めることができます。


 ということで、しばらくは退屈なチューニング作業です。


追記
 当初は計算時間1144msecでしたが、少し最適化して668msec、ループ内の処理を20回べた書きしてループ数を減らして415msecと、当初の2.75倍程度まで高速化できました。
 ちょっといじるだけでパフォーマンスが倍近くになるんだから、コレだからARMはやめられねぇぜ…。最初がどれだけ冗長なコードだったんだ、という話ですが。
 べた書きするだけでも1.6倍になるので、forのオーバーヘッドは馬鹿になりません。この部分はべた書きでforを減らせば減らすほど早くなりますが、あまりやりすぎると効率が悪化していきます。あとメンテナンス性が悪くなったり、べた書きするとメモリ長を変えた際にバッファオーバーランを起こしやすくなります。

 Cortex-M4にはSIMD命令が有るらしいので、コレを有効活用できれば2倍近く効率化できるかもしれません。とはいえ、結果は約28bit必要ですから、32bitコアのSIMDでは処理できません。うまい方法を考える必要があります。

追記
 SIMDを試してみました。668msecが715msecになりました。遅くなってんじゃん!!

 試しに、SMLADを使ってみました。これはint16_t*int16_t+int16_t*int16_t+int32_tを返す命令です。ADC値はすでに16bitで取っているので、相関値を8bitから16bitに変更し、SMLADを呼んだ次第です。よく確認してませんが、引数が増えた分、レジスタが足りなくなってるんじゃないかなーって気がします。

 今まで、ベクトル演算を使えば無条件に早くなるもんだと思いこんでいましたが、そうじゃないんだなぁ。
 特にCortex-M4のSIMDはほぼアセンブリを直接書くので、コンパイラ最適化が弱くなってしまいます。そのため、コードを書く側が最適なコードを考える必要がありそうです。使えそうな命令は他にも色々有るので、もうちょっとじっくり試してみたいと思います。

追記
 SMLADはforで回すより、べた書きしたほうが効率が高いようです。
 スカラ演算をべた書きした場合、1回の計算に3命令が必要です。一方、SMLADを使った場合は1回の計算に5命令が必要です。ただしSMLADは1回の計算で2個の変数を処理できるので、実質的に1回の計算は2.5命令で行うことができます。
 ベクトル演算をべた書きした場合、スカラ演算の1.2倍のパフォーマンスとなります。
 ベクトル演算を40組べた書きした場合、サンプリング期間50msec/パルス幅10msec/200kspsを処理するのに350msecを要しています。

 8bit符号ありレジスタを掛け算して和を返す、みたいな命令があれば更に高速に処理できると思うんですが、8bitの積を計算する命令はなさそうです。

 アセンブリを見てみると、若干謎な動作をしています。このあたりのコンパイラ最適化?をもっと最適化できれば、更に高速になりそうですが、とりあえず眠いので寝ます。
 土曜は朝早いのにまた位相ズレが。毎回こんなことしてるなぁ。

2018年2月12日月曜日

STM32F4でDACの使い方

 STM32F4でDACを使う場合。Cubeを使う想定。
 デュアルDACはたぶんHALでは使えないはずなので扱わない。


* ソフトウェアで値を設定する

 CubeのPinoutタブでPeripheralsのDACにチェックを入れる。Configurationは特に設定不要。

 最初にHAL_DAC_StartでDACの動作を開始する。その後、HAL_DAC_SetValueで値を与える。

HAL_DAC_Start(&hdac, DAC_CHANNEL_1);
HAL_DAC_Start(&hdac, DAC_CHANNEL_2);

HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_L, 65535);
HAL_DAC_SetValue(&hdac, DAC_CHANNEL_2, DAC_ALIGN_12B_L, 32767);


 SetValueの第3引数はデータのフォーマットを示し、DAC_ALIGN_8B_R, DAC_ALIGN_12B_R, DAC_ALIGN_12B_Lの3種類がある。
 8Rは0から255までの範囲を取り、12Rは0から4095までの範囲を取り、12Lは16から65535までの範囲を取る。12Lは下位4bitは無視される(一応リファレンスマニュアルには「リセット値に保持しろ」と書いてある)。

 8R,12R,12LはHALでフォーマットを変換しているわけではなく、データは直接ペリフェラルに流し込まれるので、どのフォーマットでも大してリソース量に差はない。


* TIMでタイミングを作り、DMAで転送する

 CubeのPinoutタブでPeripheralsのDACにチェックを入れる。
 使用するTIMの適当なチャンネルでPWM Generation No Outputを選択する。このタイマ/ピンはなんでも良いが、可能であればピンが未使用のチャンネルを選ぶと良い。もし正常に動作しない場合は、PWM Generationを選択すればパルスをピンに出力できるので、オシロやロジアナでデバッグできる。
 適当に、といっても、DACと接続できるTIMを選ぶ必要がある。TIM2, TIM4, TIM5, TIM6, TIM7, TIM8が接続できる。非常手段としては、DACをEXTIでトリガし、GPIO経由で接続する、ということもできるが、信頼性やコストに難がある。

 次にConfigurationタブでDACのコンフィグを表示し、Parameter SettingsタブでTriggerを先程有効化したTIMに設定する。DMA SettingsタブでAddを選択し、DMAを追加する。

 次にTIMのコンフィグを表示し、Parameter Settingsタブでプリスケーラやピリオドを設定する。またTrigger Event SelectionでOutput Compare (OCnREF)を選択する。そしてPWM Generation Channel nでPulseに1を入れる。この値はなんでも良いが、0やPeriod以上の場合はパルスが出力されない。Periodに関係なく使える値は1だが、上記のオシロ/ロジアナでデバッグする場合は、Periodの半分の値を設定しておくと良い。この場合はPeriodを変更した時にPulseも変更するのを忘れないように。


 ソフトウェアでは、HAL_DAC_Start_DMAでバッファを指定し、HAL_TIM_PWM_Startで実行を開始する。

const int dac_buffer_length = 100;
static uint16_t dac1_buffer[dac_buffer_length];
static uint8_t dac2_buffer[dac_buffer_length];

for (int i = 0; i < dac_buffer_length; i++)
{
    dac1_buffer[i] = (int)(sin((float)i / dac_buffer_length * M_PI * 2) * 2047 + 2047);
    dac2_buffer[i] = (int)(sin((float)i / dac_buffer_length * M_PI * 2) * 127 + 127);
}

HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, (uint32_t *)dac1_buffer, dac_buffer_length, DAC_ALIGN_12B_R);
HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_2, (uint32_t *)dac2_buffer, dac_buffer_length, DAC_ALIGN_8B_R);

HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_1);


 今回は動作確認のため、DMAをCircular Modeに設定し、DAC1を12bitで、DAC2を8bitで出力している。この際、DMAは、DAC1側をHalf Word、DAC2側をByteに設定している。

***

 DACはソフトウェアトリガでの動作ができるが、ソフトウェアトリガとDMAの組み合わせでは動作しない(たぶん。簡単に試した限りだと動かなかった)。
 リファレンスマニュアル(RM0090 rev5 日本語版)によると「DAC DMA リクエストは(中略)外部トリガ(ソフトウェアトリガでなく)が発生したときに生成されます」と書いてあるので、ソフトウェアトリガでDMA転送はできないはず。
 そもそもソフトウェアでDACを制御したいなら、直接SetValueで設定しろ、ということなのだと思う。

2018年2月10日土曜日

あなろぐ・こんぴゅーたー


 アナログ回路の規模がかなり大きくなってきました。
 とりあえず、DCDCを探してきて安定化電源が不要になったのと、送信側にもオペアンプを入れてみました。
 左下の黒い四角がDCDCコンバータ、左側のオペアンプが受信側、右側のオペアンプが送信側です。


 基本的に、「ノイズ対策は可能な限り早い段階で」というのが鉄則です。後段で信号を増幅しても、目的の波形を増幅しているのか、途中で入ったノイズを増幅しているのか、わけがわからなくなるためです。
 ということで、送信する前に増幅してしまえ、となったわけです。

 今まではマイコンのDACに直結して、約5.5Vpp程度でした。オペアンプを通すと、12Vpp程度になります。電圧的には倍程度ですが、電力的には大きく改善しているはずです。インピーダンスもかなり下がっているはずですしね。



 相関を取ってみると、グラフで見るとかなり低いような感じですが、これは直接波が大きく、相対的に小さく見えているだけで、実際にはかなり強く相関が取れています。2.4m先(14.5msec)の反射波で360くらいですが、送信段で増幅していない時は15程度でした。
 もっとも、相関値は実数なので、15程度でも目で見る分には十分な相関が得られますし、信号が強くなるとその分パルス圧縮幅も大きくなってしまい、距離分解能が悪化するわけですが。
 グラフ中で20msec以降の相関がゼロなのは、データ数が足りなくて相関が取れなくなるためです。


 三重大学大学院の修士論文("パルス圧縮を利用した超音波レーダの開発に関する研究")によると、40kHzの超音波を符号でパルス圧縮した場合、1mと4mの目標で標準偏差2.2mmという結果になるようです。40kHzの波長が8.5mmですから、2.2mmはおよそ位相90度に相当します。FM-CWは数波長ずれても強い相関が出てしまいますが、符号圧縮は1波長ずれると相関が弱くなり、また位相ズレ0度で最大の相関、90度で相関ゼロ、180度でマイナスの相関になりますから、1波長ズレ時の相関が弱くなれば、一気に90度の分解能が得られるようになります。
 ただ、件の論文では、正弦波1個に1bitを割り当てて、これをFSK変調しているようですから、送受信機の応答性がかなり良くないと動作しないような気がします。


 今の段階では、PCに取り込んでからC#で相関を計算していますが、マイコンで計算する場合は、FPUがあるとはいえ、実数では遅くなりそうなので、正数で計算するべきかな、と思います(STM32F4のFPUってどれくらいの速度で動くんだろう?)。
 FPGAで相関器を組む場合や、専用のASICを作る場合(GPS等)、相関器は1bitで作るようです(最近のGPSはマルチビット化してるようですが)。これはbit数が増えればそれだけ回路規模が大きくなるためですが、マイコンの場合はすでにあるALUを使うほかなく、バス幅までなら1bitだろうが複数bitだろうが計算速度に差はありません。
 STM32の場合は32bitなので、結果が32bitに収まる範囲であれば、任意のビット数で計算できます。
 ADCは符号なし12bitですが、結果は負を取るので、符号あり32bitになります。31bit-12bitで19bit分の掛け算を行うことができます。19bit、つまり524288サンプルまで計算できるとして、パルス幅を10msecとする場合、52.4Msps程度までなら、キャリー無しに計算ができます。STM32F4はADC1個で最大2.4Msps、ADC3個でも7.2Mspsまでですから、十分に余裕があります。逆算すると、パルス幅72msecまでなら計算できることになります。実際には、常にADC結果が4095に張り付く、ということはありえないので、その1.5倍程度、100msecくらいなら問題ないはずです。
 とはいえ、相関を取るのは1サンプルずつずらして総当りで計算する必要があるので、それだけの計算量を実用速度で処理できるか、という問題もありますが。あとそんなにRAM無いですし。。。

***

 とりあえず、送信出力を上げたことにより、反射波の強度もかなり高くなりました。ということで、かなり遠くからの反射波でも受信できそうな感じです。
 とりあえず、アナログ回路の大枠はこれで確定かな、という気がします。ということで、ブレッドボードからユニ基板に移植してやれば、後はソフトウェアに専念できます。あと、ユニ基板になってしまえば自由に持ち運びができるので、どれくらいの距離まで計測できるかもテストできます。

 その前に自分で温度を計測できる機能を作る必要がありますね。去年TO-92の1Wire温度センサをいくつか買ったはずなんだけど、どこに仕舞ったかなぁ。探しても出てこない。。。

2018年2月8日木曜日

空想:GT-K-9 他

 サバゲでK-9使いたいな、と思って考えてた。どうしてこうなった。
 ガスタービンエンジンで動き回る犬型ロボット。

 ガスタービンのブリードエアで各関節のエアピストンを動かして走り回る。
 稼働時はガスタービン特有のキーンという音がする。ごく短時間ならアキュムレータの圧力により、ガスタービンを停止させて稼働することができる。
 排熱はフレームを通して全身を使って放熱する。フレームむき出しなので風通しが良くて放熱性が良い。寒い時は抱けば暖を取れる。

 背中にちょっとしたペイロードスペースが有る。光学機器を載せて偵察したり、モスカートを載せたりできる。

 燃料は「揮発性ならなんでも良い」「シャネルの5番でも動くぜ!」
 燃料が減ったら勝手に燃料を飲みに行く。

 ちょっとした指示なら音声認識でできる。Bluetoothヘッドセットとか使える。
 Wi-Fiで接続すればスマートグラスに映像を表示したりできる。


 サバゲで誰かK-9やってほしいなぁと思ってたけど、犬型ロボット楽しそうだなぁ。
 椅子に座って何かやってたら膝の上に飛び乗ってきたり、邪魔だけど暖かくて下ろすに下ろせないし。

 でもサバゲでロボット使うと一気に悪役になりそうだ。

 ちょっとしたピックアップトラックのラジコンカーでもサバゲに使ったら楽しそうだけど、僕が行ってるフィールドは草が肩の高さまで有るようなところなので、グランドビークル的には厳しい。
 RQ-11とかほしいなぁ。あれスロットルとエレベーター・ラダーの3chしか無いし、光学ジンバルもオプション扱いで、基本的に3chの入門用ラジコン飛行機にカメラのダウンリンク付けただけだから、飛ばすのは簡単そうなんだよなぁ。
 だいぶ前にラジコン飛行機買った時は、国産の1.5x1.5mくらいの3ch高翼EPが2万円未満だったんだが。最近はそのクラスって売ってないよねぇ。電装系だけでその3倍くらいは軽くかかりそうなイメージ。

2018年2月6日火曜日

パルス圧縮:測距してみた


 パルス圧縮を超音波距離計に転用するには向かないだろう、という予感から、距離を計測する方向に向かっています。

 とりあえず壁に向けてパルスを送ってみました。
 一番大きなピークは0.0137秒後に帰ってきていて、音速で計算するとおよそ2.36mになります。約2.3m先に柱があり、おそらくこれの反射波だと思います。
 0.019秒後にもいくつかのピークがあり、3.25mくらいの距離になります。押し入れの壁がこのくらいの距離なので、おそらくそこの反射でしょう。ピークがガタガタしているのは、押し入れに入れたモノの乱反射だったり、押し入れの中でマルチパスが発生していたり、ということだと思います。
 0.0042秒や0.0082秒にもピークがあり、これは約72cm、1.4mに相当します。一応その距離にそれなりの面積のモノがあるので、そこからの反射波なのかな、という予想はつきます。

 ピーク検出のアルゴリズムを考える必要がありますが、人間が目で見る分にはかなり性能が良さそうです。

 相関を取るには、DAC/ADCが150kspsでパルス幅が20msec、レンジが約20m(130msec)の場合、3k*19.5kで、60M回くらいの乗算を行う必要があります。これにデータR/Wの時間もかかるので、マイコンで処理するのはかなり大変そうです。
 相関を取るのはひたすら乗算と加算を繰り返すしか無いので、マイコンには厳しい作業です。でもARMコアだと加算と乗算が1命令でできるので、その分は他のマイコンと比べれば楽なのかな?



 動作確認なので、手持ちのオペアンプを使って簡単に増幅回路を組んでみました。
 4558DDという種類で、動作電圧は±4Vなので、3.3Vや5Vでは動作しません。
 今回は安定化電源から12Vを与え、7809で9Vに落としてオペアンプを動作させています。増幅率は100倍固定で、出力を3分の1に分圧して、マイコンのADCに入力しています。
 1mから3m程度の距離であれば、100倍固定でも問題ありません。1.5m程度であれば、150mVppくらいでしょうか。2.4m程度なら70mVppくらいです。この程度でも十分に相関がとれます。1mで2.5Vppくらいになるようなゲインにしておけば、かなり長距離でもゲイン調整なしに測距できるかもしれません。

 秋月の超音波距離計キットは、1段目が100倍、2段目が5-20倍で、合わせて500-2000倍程度の増幅率です。

 今回はオペアンプの1回路しか使っていないので、あと100倍くらいは増幅できます。超音波の衰退がどれくらいなのかわかりませんが、単純計算で10倍位の距離までは測距できそうです。現在のレンジが5mとして、50mくらいまでは測距できそうかな、というところです。秋月のキットが3m程度、調整次第で10m程度まで、ということなので、予想通りに行けば5倍の性能になります。とはいえ、実際はそこまでの性能は出ないでしょうから、15m-20mくらい測距できれば十分かな、と思っています。50mとかになると、リファレンスの距離を測るのも大変ですし。


 十分な距離でテストするには屋外に出る必要がありますが、今の時期は外に出たくありません。あと超音波距離計は個人的に需要がないので、完全に興味本位です。ということで、飽きたらそこで終了です。ま、いつものことですね。

2018年2月5日月曜日

パルス圧縮


 超音波でパルス圧縮、中心周波数41kHzくらいでΔF2kHz、パルス幅20msecで、パルス圧縮比100くらい。DAC/ADCは100kHzで通してる。サンプリング周波数が中心周波数の2.4倍程度だから、波形を見ても波には見えない。けどちゃんと相関取れてる。

 相関の結果がいい感じにサイドローブ有って、教科書通りという感じ。

 FFTの結果はちょっと広がってる。サンプリング周波数が低い影響?

 フィルタリング後のパルス幅は1msecくらい。20msecを100倍して1msecなので、ちょっと広い感じ?


 ADCのバッファは150msecくらいかな。超音波距離計なら50mくらいのレンジ。
 ただ、後述の理由で、パルス幅が20msecなら最短距離は3.4mとなる。この距離以内に有る目標は探知することができない。近距離で使うなら、パルス幅を短くする必要がある。


 パルス圧縮でフィルタリング後が1msecとして、「精度が1msec」ということではない。「分解能が1msec」ということになる。1msecで超音速なら35cmくらいの分解能。
 2つの物体の距離が35cm以上離れていれば、分けて認識することができる。それより近い距離にある物体は、1つとして認識してしまう。
 ということになるはず。反射波で試したことがないので正しいところは不明。

***

 某艦載レーダーはパルス幅が最大51usec、最小6.4usecで、PCRは128。となると、最大0.4usec、最小0.05usecくらいの分解能になる。0.4usecなら120m、0.05usecなら15m程度の分解能になる。パルス幅が短ければ分解能が高く、パルス幅がながければ遠くまで検出できる。はず。

 パルス幅が長いと、自分が送信中に反射波が帰ってきてしまう。例えば51usecなら約7.5km以内の物体は、レーダーを送信中に反射波が返ってくるので、この距離より近い目標を探知することができない。6.4usecなら、1km以内の物体は見えない。
 艦隊防空で1km未満まで接近されると迎撃できない、と言うのはちょっと不安な感じだけど、そもそもその距離なら対空ミサイルの最短射程を下回っているだろうし、RAMの最短距離に迫る距離となる。1kmは結構長い距離だけど、この範囲内の目標を探知する必要はない、ということなんだろう。そもそもその距離まで接近された時点で負けだろうし。

***

 超音波でパルス圧縮、チープなマイコンだと厳しい気がしてきた。とは言えPCで処理するにしても、シリアルポートがボトルネックになる。

 とりあえず、今はDACのバッファを無駄に大量に使っているし、ADCも2ch分取っている。測距するだけなら、ADCは1chでいいし、外部のオペアンプとかで逆位相を作るならDACのデータも半分で済む。ADCの前にオペアンプを付けてフルスイングさせればADCの分解能も低くていいから、データ量は半分になる。
 ということで、更新レートが低くていいなら、マイコンでも簡単なパルス圧縮はできそうな気がする。
 でもモノパルスシーカーとか作るとなると、どうかなぁ。

2018年2月4日日曜日

ベジェ

 C#でベジェ曲線っぽいものを書きたくなったので試しに。
 ベジェ曲線の書き方自体うろ覚えで、しかも寝起きで書いてるので変な所あるかも。



 なんとなくそれっぽい感じに書けてるから、大丈夫じゃないかなぁって気がする。
 今回はそれっぽい曲線が書ければ十分なので、これで大丈夫ということにしておく。

 計算のpublic関数で、intとdoubleで多重定義しているので、そこだけ注意。
 それぞれの線の最大長さを指定する方法と、分割数を指定する方法の2種類がある。
 前者は計算量が多い代わりに、なめらかな曲線になる。後者は計算量が少ない代わりに、ガタガタした曲線になりやすい。
 上画像では、黒や白は前者で、緑は後者で計算している。


2018年2月3日土曜日

パルス圧縮


 簡単に正弦波で計算してみた。
 見たことある形になってる。
 ある程度接近していてもそれなりにピークが出てる。あんまり近すぎると打ち消す方向になってしまうけど。

 チャープとか符号とか色々あるけど、結局のところGPSとかのCDMAと全く同じもの(雰囲気の話)。


 単純なチャープ以外にも、符号でパルス圧縮を行う方法もあるらしい。これの場合、複数の送信系で相関の低い符号を使うことにより、クロストークの対策とすることができるらしい。複数の超音波距離計を符号多重にして全周に向けて取り付けて、全方位を測距できるロボット向けのセンサに、とかもあるんだとか。

 時間精度は、位相が180度ずれるとマイナスにピークが出て、かつ相関が低いのでピークは小さくなる。位相が90度ずれるとピークが小さくなる。位相のズレが無いときが一番ピークが高く出る。ということで、タイミング精度は位相誤差90度未満ということになる。
 入力値が複数のbitで表現されている場合、DCオフセットが有ってもそれなりにちゃんと計算できるっぽい。HPF通してDC成分切らなきゃいけないかと思ってたけど、必ずしもその必要はないみたい。フィルタは計算コスト高いので不要ならそれに越したことはない。


 次の課題としては、マイコンで処理しやすいような相関処理を作ること、その計算負荷がどれくらいかを確認すること、あたりか。
 超音波測距をするだけなら相関器は1個でいいけど、モノパルスシーカーを作るなら相関機は4組必要になる。
 最悪、マイコン1個に相関器が1個しか組めないなら、受信1chあたりマイコン1個、合計4-5個使う、という手も有る。嫌だけど。

 超音波風速計に使うなら、0.2フェーズくらいだとちょっとタイミング精度悪いかも。最終的には波形データから直接ゼロクロス点を探す必要があると思う。とはいえ、今まではゼロクロスを検出してもそれがどのフェーズなのかがわからなかったから、パルス圧縮を組み合わせればそのあたりの信頼性は向上するはず。


 パルス圧縮は船舶レーダーとか、エコー検査とか、いろんなところに使われているらしく、様々なPDF資料が出てくる。どれも全部同じように難しくてよくわからんが。


 相関の計算がちょっと自信なくて、もっと別のやり方をするべきじゃないか、という気がしてきたけど、とりあえず今のままでも動いてるので、まずはこの方式でもうちょっと試してみる予定。


追記:2018/02/04


 試しにパルス圧縮比(PCR)128の波形を作ってみる。サンプリングレート1MHz、周波数100kHz、ΔF100kHz、パルス幅1.28msecの想定。
 正弦波は±1の範囲だけど、ノイズとして±5の乱数を加算しても、ちゃんと相関が取れてる。かなりSNR悪くてもちゃんと動作しそう。計算量凄まじいけど。

 AN/SPY-1はPCR128で、最小パルス幅は6.4usだが、このパルス幅ならΔFは20MHzになる。3.3GHzの内の20MHzはかなり小さい変化幅。51usなら2.5MHz程度で済む。フェーズドアレイレーダーは周波数に応じて遅延量を調整するから、あまり周波数が大きく変化するとマズいんだろう。あと大電力発振器の特性の問題とかも有るんだろう。


 とりあえず、リニアのパルス圧縮はなんとなくわかるようになってきたかな。本当に正しい理解なのかは分からないけど。
 amazonでレーダー関連の本を探してみるとそれなりにお高い。仕事で使うなら安い分類だろうけど、ちょっと興味本位で買うのはなぁ、っていう値段。その辺りは追々。

DAC/ADC

 STM32F4のDACとADCをPCから制御するファームウェアを作って、いろいろ波形をだして遊んでいる。
 仕様としては、DACmax2Msps、ADCmax1.4Msps、DAC1.4Msps/ADC1.4Msps時10msec分のバッファがあり、双方とも2chを同時に入出力できる。
 DAC2Msps/ADC1.4Mspsだと正常に動作しない。おそらくDMA転送能力の問題。

 マイコンの仕様ではADCを最大2.4Mspsで動作させることができるようだが、コアクロック168MHzでは1.4Mspsが最大。2.4Mspsで使いたい場合はコアを144MHzにする必要がある。また3個のADCを順番に使うことにより、最大7.2Mspsでサンプリングすることができる。
 ADC変換はピークホールド時間+二分探索の分の時間がかかり、ピークホールドは最小3サイクル、二分探索は12bitで12サイクルが必要になる。168MHzではADCクロックは21MHzなので、21/(3cycle+12bit)で1.4Mspsが最大となる。144MHzの場合はADCクロックが36MHzで2.4Mspsとなる。
 STM32F4ではADC6bitモードがあるので、6bitで使うなら36/(3cycle+6bit)で4Mspsまで設定できて、トリプルADCで12Mspsまでサンプリングできて… となる気がするけど、実際はできるんだろうか? データシートには「up to 24 channels and 7.2 MSPS in triple interleaved mode」としか書いてない。12Mspsでサンプリングできるならそうアピールしそうなものなので、できないのかも。

 昔計算したところによると、超音波風速計で0.05m/sの分解能を得るには20Mspsが必要らしい。仮に12Mspsでサンプリングできるとしたら、5個くらいの位相を平均すれば20Mspsくらいの分解能は得られそう。実際には数百波くらいを計測できるから、かなり高分解能になりそうな気がする。


 今遊んでるPC側のソフトは、DACの波形を生成したり、ADCの波形をFFTに通したり、といった機能を作っている。FIRとかを追加すれば、パルス圧縮とかも試せそうな気がする。
 試しにパルス繰り返しを試してみたけど、こっちはあんまりうまく動いてない。
 パルス繰り返しはドップラーレーダーに有効な機能な気がするし、パルス圧縮もレーダー用途で、正弦波1周期未満の分解能を得るモノでは無い気がする。ただ、パルス圧縮が思ってる通りの機能なら、初期捕捉の正確性が向上しそうな気がするので、ちょっと試してみたい。

 「パルス繰り返し周波数」は軍用レーダーでよく使われる用語だけど、医療用のエコー検査機器でも使われるらしい。軍用のドップラーレーダーは静止目標(地上物等)を除外するためのものだが、医療用エコーでは静止物(体組織)を除外して、移動物(赤血球等)を見るための機能らしい。エコー検査の資料は探せば出てくるので、パルス繰り返し周波数の能力を知りたい方はそちらをどうぞ~。たぶん軍用レーダーの資料を探すより早いはず。


 超音波風速計を作るなら、13倍程度の増幅が必要。この程度ならオペアンプ1段で余裕だろうから、2回路入りの高速オペアンプを3個で足りるはず。ただ超音波風速計は個人的にあまりおもしろい題材じゃないので、優先度は低め。

 モノパルス・ドップラー・超音波レーダーとか作りたい。コレを2軸ジンバルに乗せて、電動ガンも乗せれば、サバゲで陣地の自動防衛ができる。接近目標(=敵)は攻撃して、静止目標は検出しない。HITしたら遠ざかる方向に歩く。
 コレの攻略をするなら、首振りを見て、自分の方を向いたら動くのをやめる、という感じか。意外と簡単に接近できそう。
 オープンループだけど、雰囲気としてはLPWSに近い。電動ガンのミニガンとか使いたいけど、とりあえずマルイのミニ電動ガンとかかなぁ。それでもかなりデカいジンバルが必要な気がする。


 地上物体はグランドクラッターが強いので、静止目標を迎撃するにはかなりの分解能が必要になる。ゲームとかでは自動迎撃ターレットが有るけど、あれは多分FLIRとか使っているんであろう。であるなら、その辺の窓ガラスを外して手に持てば、自動ターレットに迎撃されずに近づくことができるはず。ドップラーレーダーとFLIRを併用してたら無理だけどね。


 超音波で測距するなら40dB前後くらいの増幅が必要。オペアンプ2-3段くらい必要になる。モノパルスだと受信が4chくらい必要になるので、オペアンプ8-12回路くらいか。AGC回路も必要になるので、ちょっと面倒な感じ。FFTにしろFIRにしろ、かなりの計算能力も必要なので、STM32F4で処理できるか怪しい所。

 最近はFPGAでハードウェアFFTを組むのがトレンドらしい(?)。ドップラーレーダー的なモノを作るならFPGAを使うべきなんだろうなぁ。
 電光掲示板のときも思ったけど、ハードウェアでガリガリやりたい、と思うことがある。ただ、なんとなくFPGAは敷居が高い印象。値段も高い印象。マイコンと比べて1-2桁くらい高価なイメージ。あと昔FPGA基板が付属した入門本を買ったら、「転送ケーブル買ってね(^^」(くっそ高い)、ということが有って、ちょっとトラウマ気味。


 とりあえず、しばらく超音波で遊んで見る予定。


追記
 パルス圧縮、全く違う理解をしてるっぽいゾ。
 PRFは参考にしたPDFに近い特性が出てきた。けど超音波素子に1フェーズしか入れてないのに50フェーズくらい受信する。ものすごい衰退が遅い。この素子でパルス繰り返し周波数云々の機能は無理っぽいなぁ。


追記

 1.4MspsのDAC(右)とADC(左)のFFT結果。
 40kHzの正弦波を1msecの長さで送信。
 128ポイントなのでものすごい荒い。パルス圧縮ではなく、固定周波数。
 DACからおよそ0.5msec遅れてADCにも波形が出てる。センサ間は15cmなので、音速では440usほど。だいたい計算通りか。
 ADCはなかなか発振が止まらなくて、だいぶ尾を引いてる感じ。パルス圧縮は、この収束の遅さがかなり厳しそう。
 この超音波素子でパルス圧縮は難しいかもしれない。少なくとも、受信素子はもっと応答性の良いものを使う必要がありそう。

2018年2月2日金曜日

メモ:タスクバーのアイコンの一部が表示されない

 Win10で、タスクバーのアイコンの大きさが異常となり、一部のアイコンが表示されない、という症状がでた。
 アイコンが増えると、自動的にアイコンの大きさを縮小してすべて表示できるようになっているが、この自動縮小がコケたっぽい。

 解決策としては

 1) タスクバーの適当な場所を右クリックし、「すべてのタスクバーを固定する」のチェックを外す。
 2) タスクバーのフチにマウスを動かし、カーソルが大きさ変更のアイコンに変わったところで、ドラッグする。
 3) 再びドラッグし、元の大きさに戻す。
 4) 「すべてのタスクバーを固定する」のチェックを元に戻す。

 という手順で修正することができた。
 タスクバーの高さを変えた際に、表示できるアイコンの数が変わったため、アイコンの大きさの再計算が行われ、正しい大きさになった、ということだと思う。

 こういう症状は記憶に無いので、確率としてはかなり低いのだろうが、とりあえずメモ。