ESP32+BGT60ATR24AIPでFIFO先頭データが固定される問題を調査・解決した記録

ESP32からInfineonの60GHzミリ波レーダーセンサー「BGT60ATR24AIP」を制御していたところ、FIFOから取得したフレームの先頭データが毎回同じ値になる問題が発生しました。

SPI通信やDMA転送は成功しており、フレーム後半のデータは毎回変化します。しかし、フレーム先頭の数十サンプルだけが固定されます。

最終的には、BGT60ATR24AIPのADC初期化待ち時間であるT_INIT0が、データシート記載の最小時間を満たしていなかった可能性が高いと判明しました。

この記事では、次の内容を一通りまとめます。

  • 発生した症状
  • SPIクロックエラーとの切り分け
  • FIFO停止・再開処理の検証
  • DMA転送と12bitアンパック処理の確認
  • LFSRテストによる原因範囲の特定
  • PLLセトリング時間とTR_STARTの検証
  • T_INIT0設定の問題発見
  • 最終的な修正内容
  • 今後確認すべき項目

使用環境

今回使用した主な構成は次のとおりです。

  • MCU:ESP32シリーズ
  • レーダーIC:Infineon BGT60ATR24AIP
  • 通信方式:SPI
  • SPIクロック:20MHz
  • FIFO読出し:DMA
  • ADC分解能:12bit
  • RXアンテナ数:4
  • TXアンテナ数:2
  • 1チャネルあたりのサンプル数:256
  • 1フレームあたりの合計サンプル数:2048

1フレームのデータ数は、次の計算になります。

256サンプル × 4RX × 2TX = 2048サンプル

最初に発生した症状

当初は、データ送信を実行した後、BGT60ATR24AIPのFIFO読出しで次のエラーが発生していました。

BGT60: -> CLK_NUM_ERR: SPI clock count error
BGT60: no data. return 4

ログの例です。

I (41314) bgt60: send start
I (42382) esp-x509-crt-bundle: Certificate validated
I (43840) SEND_MM: Data sent successfully
W (44341) BGT60: -> CLK_NUM_ERR: SPI clock count error
W (44341) BGT60: no data. return 4
I (44492) BGT60: Frame restarted

この時点では、次のような原因を疑いました。

  • データ送信中もレーダー測定が続いている
  • FIFO割り込み通知がFreeRTOS側に残っている
  • レーダー停止と再開の順序が適切でない
  • FIFOに必要なデータが蓄積される前に読み出している
  • SPI DMA転送量とサンプル数が一致していない

FreeRTOSのタスク通知が残る可能性

FIFO割り込みでは、ISRからミリ波処理タスクへ通知を送っていました。

vTaskNotifyGiveFromISR(mmwaveTaskHandle, &higher_priority_task_woken);

ミリ波処理タスク側では、次のように通知を待ちます。

uint32_t irq_num = ulTaskNotifyTake(
    pdTRUE,
    portMAX_DELAY
);

pdTRUEを指定すると、通知値を取得した後でゼロへクリアします。

ただし、通知の取得後に別のFIFO割り込みが発生すれば、新しい通知値が再び残ります。

そのため、レーダー停止時には次の順序を採用しました。

GPIO割り込み無効化
→ BGT60のFSM/FIFO停止
→ 残っているタスク通知を破棄
→ データ送信
→ BGT60再開
→ GPIO割り込み有効化

残った通知は、次のコードで破棄できます。

uint32_t discarded = ulTaskNotifyTake(pdTRUE, 0);

この変更によりCLK_NUM_ERRの発生頻度は大きく下がりました。

特に、BGT60を停止する処理を送信タスクではなく、優先度の高いミリ波処理タスク内へ移動したことで改善しました。

GPIO割り込み制御の順序を修正

最終的に安定した停止・再開順序は、次のようになりました。

停止時:

gpio_intr_disable(PIN_XENSIV_BGT60TRXX_IRQ);

int32_t ret = xensiv_bgt60trxx_start_frame(
    &bgt60_obj.dev,
    false
);

uint32_t discarded = ulTaskNotifyTake(pdTRUE, 0);

再開時:

uint32_t discarded = ulTaskNotifyTake(pdTRUE, 0);

int32_t ret = xensiv_bgt60trxx_start_frame(
    &bgt60_obj.dev,
    true
);

if (ret == XENSIV_BGT60TRXX_STATUS_OK) {
    gpio_intr_enable(PIN_XENSIV_BGT60TRXX_IRQ);
}

重要なのは、再開前に残存通知を消し、測定開始に成功してからGPIO割り込みを有効化することです。

この修正によってSPIクロックエラーは大きく改善しました。

しかし、その後に別の問題が見つかりました。

FIFO先頭のデータが毎回固定される

FIFOから2048サンプルを取得し、先頭を表示したところ、毎回同じ値が出力されていました。

BGT60_UNPACKED:
0000 0000 0000 0000 0000 0490 00ff 0c74

一方で、FIFO全体のチェックサムや中央・末尾のデータは毎回変わります。

BGT60_PACKED:
checksum=0x8cef8bd6
head=0000 0000 0000 0400
mid=5682 88de dca5 a682
tail=95bb 8571 65f8 a22b

次のフレームではチェックサムが変化していました。

checksum=0x01d3ffc6
checksum=0xb2cfd4f9
checksum=0x60135e16

この結果から、次のことが分かりました。

  • DMA転送自体は実行されている
  • FIFO全体が古いデータのままではない
  • フレーム後半は毎回更新されている
  • 先頭付近だけが固定されている

停止・再開処理が原因か確認

当初は、測定停止や再開によってFIFO先頭に古いデータが残っている可能性を疑いました。

そこで、レーダーの停止・再開処理を一時的に無効化し、連続測定を行いました。

しかし、停止・再開を行わなくても先頭データは固定されたままでした。

BGT60_PACKED:
head=0000 0000 0000 0000

その一方で、中央と末尾は変化しました。

この結果から、先頭固定問題は次の処理とは直接関係しないと判断しました。

  • xensiv_bgt60trxx_start_frame(..., false)
  • xensiv_bgt60trxx_start_frame(..., true)
  • FIFO停止と再開
  • データ送信時間
  • gRPC/HTTP2通信

SPI DMA転送前のバッファ初期化

DMAが本当に受信バッファを書き換えているか確認するため、SPI転送前にバッファを既知値で埋めました。

memset(
    rx_data,
    0xA5,
    len * sizeof(uint16_t)
);

FIFO読出しは次のように実行しています。

spi_transaction_t t;
memset(&t, 0, sizeof(t));

t.length = len * 12;
t.rxlength = len * 12;
t.tx_buffer = NULL;
t.rx_buffer = rx_data;

esp_err_t status =
    spi_device_polling_transmit(esp_iface->spi, &t);

2048個の12bitサンプルを読み出すため、転送量は次のようになります。

2048 × 12bit = 24576bit
24576 ÷ 8 = 3072byte

ログでも3072byteが毎回転送され、バッファ全体のチェックサムが変化しました。

status=ESP_OK
bytes=3072
checksum=毎回変化

この結果から、DMAバッファが更新されていない可能性は除外できました。

12bitパックデータのアンパック確認

BGT60ATR24AIPのADCデータは12bitです。

SPIでは12bitデータが連続してパックされるため、ESP32側で16bit配列へアンパックします。

uint32_t last_index =
    div_ceil_u32(len * 12U, 16U) - 1U;

unpack_samples(
    rx_data,
    last_index,
    len
);

アンパック前後のデータを表示しました。

BGT60_PACKED:
head=0000 ff12 ffff ffff

BGT60_UNPACKED:
0000 0012 0fff 0fff

アンパック後も先頭固定が残ったため、表示処理だけの問題ではないと考えました。

フレーム間で最初に変化する位置を確認

前回フレームと今回フレームを比較し、最初に値が変化した位置を調べました。

static uint16_t prev[64];
static bool prev_valid = false;

if (prev_valid) {
    int first_diff = -1;

    for (int i = 0; i < 64; i++) {
        if (rx_data[i] != prev[i]) {
            first_diff = i;
            break;
        }
    }

    ESP_LOGI(
        "BGT60",
        "first changed sample=%d",
        first_diff
    );
}

memcpy(prev, rx_data, sizeof(prev));
prev_valid = true;

結果は次のようになりました。

first changed sample=34
first changed sample=37
first changed sample=57

この時点で、先頭数十サンプルが毎回固定され、その後からデータが変化していることが分かりました。

なお、この比較はアンパック前に行っていたため、厳密には「ADCサンプル番号」ではなく「パック済み16bitワードの位置」です。

最終的な確認では、アンパック後に比較するほうが正確です。

SFCTL設定の書き方を確認

設定ファイルでは、SFCTLが次の形式で記載されていました。

0x0d1083ffUL

この値には、SPI書込み形式のアドレス情報と24bitのレジスタデータが含まれています。

xensiv_bgt60trxx_set_reg()へ渡す場合は、アドレスとデータを分ける必要があります。

xensiv_bgt60trxx_set_reg(
    &bgt60_obj.dev,
    0x06,
    0x001083ffUL
);

これは正しい書き方です。

設定配列:0x0d1083ff
アドレス:(0x0d - 1) / 2 = 0x06
データ :0x1083ff

同様に、PLL Shape設定は次のアドレスになります。

0x6f13f310 → アドレス0x37、データ0x13f310
0x7f13f310 → アドレス0x3f、データ0x13f310

したがって、直接設定する場合は次のように記述します。

xensiv_bgt60trxx_set_reg(
    &bgt60_obj.dev,
    0x37,
    0x0013f310UL
);

xensiv_bgt60trxx_set_reg(
    &bgt60_obj.dev,
    0x3f,
    0x0013f310UL
);

0x6f0x7fをそのままレジスタアドレスへ指定すると、別のアドレスへ書き込んでしまいます。

LFSRテストでFIFO以降の経路を切り分ける

原因がBGT60のADC側にあるのか、FIFO・SPI・DMA側にあるのかを切り分けるため、LFSRテストを実施しました。

LFSRは「Linear Feedback Shift Register」の略で、BGT60内部で疑似ランダムなテストデータを生成する機能です。

通常測定:

アンテナ
→ アナログ受信回路
→ MADC
→ FIFO
→ SPI
→ DMA
→ ESP32

LFSRテスト:

LFSRテストデータ
→ FIFO
→ SPI
→ DMA
→ ESP32

つまり、LFSRではアンテナ・アナログ回路・MADCを迂回できます。

テスト中だけ、SFCTLのLFSR_ENビットを有効化しました。

xensiv_bgt60trxx_set_reg(
    &bgt60_obj.dev,
    0x06,
    0x001283ffUL
);

通常値は次です。

0x001083ffUL

LFSR有効時は、次のような結果になりました。

BGT60_PACKED:
head=b788 7458 dab7 5744

BGT60: first changed sample=0

BGT60_UNPACKED:
088b 0758 074b 07da 0445 0734 06ee 07e4

次のフレームでも先頭から変化しました。

BGT60_PACKED:
head=07ea 745a f2c7 0775

BGT60: first changed sample=0

BGT60_UNPACKED:
0ea0 075a 074c 07f2 0750 0734 06f3 07e4

この結果から、次の処理は正常と判断できました。

  • FIFO
  • SPIバースト読出し
  • DMA
  • 12bitアンパック
  • ESP32側バッファ処理

原因範囲は、LFSRが迂回した部分へ絞られました。

アナログ受信回路
MADC
FSM
チャープ開始タイミング
ADC初期化・キャリブレーション時間

LFSR試験終了後は、SFCTLを通常値へ戻しました。

xensiv_bgt60trxx_set_reg(
    &bgt60_obj.dev,
    0x06,
    0x001083ffUL
);

PLLセトリング時間を延ばして確認

次に、Shape1とShape2のPLLセトリング時間を確認しました。

設定ファイルには次の値がありました。

0x6f100310UL
0x7f100310UL

元の設定候補として次の値も残っていました。

0x6f13f310UL
0x7f13f310UL

PLL設定を元の長い時間へ戻しました。

xensiv_bgt60trxx_set_reg(
    &bgt60_obj.dev,
    0x37,
    0x0013f310UL
);

xensiv_bgt60trxx_set_reg(
    &bgt60_obj.dev,
    0x3f,
    0x0013f310UL
);

しかし、この変更だけでは先頭固定は解消しませんでした。

したがって、PLLのローカルなセトリング時間だけが原因ではないと判断しました。

TR_STARTを変更して確認

次に、CCR1のTR_STARTを増やしました。

TR_STARTは、ランプ生成前の遅延時間です。

公式データシート上の計算式は次です。

T_START =
(TR_START × 8 + 10) × T_SYS_CLK

T_SYS_CLK=12.5nsです。

現在の設定は、

0x5b60ea0aUL

で、データ部分は次です。

CCR1 = 0x60ea0a
TR_START = 0x0a

試験ではTR_STARTを大きくしました。

uint32_t ccr1 = 0;

xensiv_bgt60trxx_get_reg(
    &bgt60_obj.dev,
    0x2d,
    &ccr1
);

ccr1 &= ~0x0001ffUL;
ccr1 |= 0x000100UL;

xensiv_bgt60trxx_set_reg(
    &bgt60_obj.dev,
    0x2d,
    ccr1
);

この変更によって、先頭がすべてゼロになる状態から、非ゼロの固定値が出る状態へ変化しました。

BGT60_UNPACKED:
01c0 0e20 00f4 0410 0000 090f 020f 0001

しかし、値は依然としてフレームごとに固定されていました。

この結果から、タイミング設定が症状へ影響していることは分かりましたが、TR_STARTだけでは根本解決になりませんでした。

先頭値がMAIN・ADC0設定値と一致して見えた

固定値を24bit FIFOワードとして組み直すと、次のようになりました。

01c0 0e20 → 0x1c0e20
00f4 0410 → 0x0f4410

設定ファイルには、次の値があります。

0x011c0e20UL; // MAIN
0x030f4410UL; // ADC0

データ部分だけを見ると完全に一致します。

MAIN = 0x1c0e20
ADC0 = 0x0f4410

そのため、一時はレジスタ設定データがFIFOへ混入している可能性を疑いました。

しかし、その後の値は、

0x00090f
0x20f001

となり、設定ファイルのレジスタ列とは一致しませんでした。

また、BGT60ATR24AIPのFIFO Prefix機能が生成するヘッダは、公式仕様では次の構造です。

同期ワード
フレームカウンタ
Shapeグループカウンタ
チャープ長
SADC値
Shape番号

したがって、今回の0x1c0e200x0f4410は、通常のPrefixヘッダとも一致しません。

最終的には、レジスタデータがFIFOへ混入したと断定せず、ADC初期化前の無効な固定出力が偶然同じ値として見えていた可能性も含めて調査を続けました。

根本原因候補:T_INIT0がデータシート要件を満たしていなかった

改めてBGT60ATR24AIPの公式データシートとCCR3設定を比較しました。

現在の設定は次でした。

0x5fbf3e1eUL

設定配列の先頭0x5fはSPI書込み形式のアドレス情報で、実際のCCR3データは次です。

CCR3 = 0xbf3e1e

CCR3には次のタイマーが含まれます。

  • TR_PAEN
  • TR_SSTART
  • TR_INIT0
  • TR_INIT0_MUL

現在値をデコードすると、

TR_INIT0     = 126
TR_INIT0_MUL = 2

でした。

T_INIT0の計算式は次です。

T_INIT0 =
(
    TR_INIT0 × 2^TR_INIT0_MUL × 8
    + TR_INIT0_MUL
    + 3
)
× T_SYS_CLK

現在値を代入すると、

T_INIT0
= (126 × 2^2 × 8 + 2 + 3) × 12.5ns
= 4037 × 12.5ns
≒ 50.5µs

ところが、BGT60ATR24AIPのデータシートには次の注意があります。

起動後のT_INIT0は、標準的なADCキャリブレーション時間に基づき、
少なくとも70µs必要

つまり、現在の約50.5µsは、データシートが要求する70µsを満たしていませんでした。

これは、ADCの初期化やキャリブレーションが完了する前に最初のサンプリングが始まり、先頭データが固定値になる現象と整合します。

T_INIT0を約101µsへ変更

TR_INIT0_MULを2から3へ変更しました。

設定変更後のCCR3は次です。

CCR3 = 0xff3e1e

コードから直接設定する場合は次のようになります。

int32_t ret = xensiv_bgt60trxx_set_reg(
    &bgt60_obj.dev,
    0x2f,
    0x00ff3e1eUL
);

uint32_t ccr3 = 0;

xensiv_bgt60trxx_get_reg(
    &bgt60_obj.dev,
    0x2f,
    &ccr3
);

ESP_LOGI(
    "BGT60",
    "CCR3 write=%ld readback=0x%06lx",
    (long)ret,
    (unsigned long)ccr3
);

変更後は、

TR_INIT0     = 126
TR_INIT0_MUL = 3

となります。

計算すると、

T_INIT0
= (126 × 2^3 × 8 + 3 + 3) × 12.5ns
= 8070 × 12.5ns
≒ 100.9µs

これでデータシートの最低70µsを十分に満たします。

設定ファイルへ反映する場合は、次のように変更します。

// 変更前
0x5fbf3e1eUL,

// 変更後
0x5fff3e1eUL,

T_INIT0変更後、先頭固定が解消

変更後の通常測定ログです。

BGT60_PACKED:
head=c783 7f56 3e88 b783

BGT60: first changed sample=0

BGT60_UNPACKED:
083c 0756 07f8 083e 083b 0726 07f0 085a

次のフレームでは、先頭から異なる値が得られました。

BGT60_PACKED:
head=6783 7f59 48a8 4783

BGT60: first changed sample=0

BGT60_UNPACKED:
0836 0759 07fa 0848 0834 072a 07f5 085b

さらに次のフレームでも変化しています。

BGT60_UNPACKED:
082a 0754 07f6 0842 082e 072a 07ee 085d

結果として、

first changed sample=0

が継続して出るようになりました。

これは、フレーム先頭からデータが毎回更新されていることを示します。

最終的な原因

今回の問題は、BGT60ATR24AIPのCCR3に設定されていたT_INIT0が短すぎたことが主原因だった可能性が高いです。

修正前:

T_INIT0 ≒ 50.5µs

データシート要件:

T_INIT0 ≧ 70µs

修正後:

T_INIT0 ≒ 100.9µs

ADCの起動・キャリブレーションが完了する前にサンプリングが始まっていたため、フレーム先頭に無効な固定値が入っていたと考えられます。

最終的に採用する設定

設定ファイルでは、CCR3を次の値へ変更します。

0x5fff3e1eUL,

直接設定する場合は次です。

xensiv_bgt60trxx_set_reg(
    &bgt60_obj.dev,
    0x2f,
    0x00ff3e1eUL
);

ただし、設定ファイルへ反映した後は、init_sensor()内の一時的な直接書込みは削除します。

同じレジスタを複数箇所で設定すると、後からどの値が最終的に有効なのか分かりにくくなるためです。

BGT60ATR24AIPのレジスタ設定で注意すること

設定配列の値と、xensiv_bgt60trxx_set_reg()へ渡す値は形式が異なります。

設定配列:

0x5fff3e1eUL

直接書込み:

xensiv_bgt60trxx_set_reg(
    &bgt60_obj.dev,
    0x2f,
    0x00ff3e1eUL
);

アドレスは次の式で求められます。

(先頭1byte - 1) / 2

例:

0x0d → 0x06
0x5f → 0x2f
0x6f → 0x37
0x7f → 0x3f

不要になったテストコード

問題解決後は、次のデバッグコードを整理します。

  • LFSRの強制有効化
  • TR_START=0x100の試験設定
  • PLL設定の一時的な直接書込み
  • 毎フレームのPACKED/UNPACKEDログ
  • 全フレームチェックサム
  • first changed sampleログ
  • DMA前の0xA5によるバッファ初期化

ただし、エラー調査用に次のログを条件付きで残すのは有効です。

if (res != XENSIV_BGT60TRXX_STATUS_OK) {
    ESP_LOGW(
        "BGT60",
        "FIFO read failed: %ld",
        (long)res
    );
}

また、CLK_NUM_ERRFIFO overflowFIFO underflowは個別に判定できるようにしておくと再調査しやすくなります。

今後の長時間試験で確認すること

現時点では先頭固定が解消していますが、量産や実運用へ進む前に次を確認します。

電源再投入試験

  • ESP32とBGT60ATR24AIPを完全に電源断する
  • 再投入後も先頭から変化するか確認する
  • 複数回繰り返す

長時間連続測定

  • 数時間連続して測定する
  • CLK_NUM_ERRが発生しないか確認する
  • FIFO overflow/underflowを監視する
  • 先頭固定が再発しないか確認する

通信送信を含めた試験

  • ミリ波測定
  • ミリ波停止
  • 圧縮
  • LTEまたはWi-Fi送信
  • ミリ波再開

この一連の処理を有効に戻し、停止・再開後も正常か確認します。

温度変化試験

ADCキャリブレーション時間は、温度や電源条件の影響を受ける可能性があります。

  • 低温時
  • 高温時
  • 電源電圧変動時
  • LTE送信による電源負荷変動時

でも正常に動作するか確認します。

T_INIT0を最低値ぎりぎりの70µsではなく、約101µsとしたことで、一定の余裕を確保しています。

まとめ

ESP32とBGT60ATR24AIPを組み合わせたシステムで、FIFO先頭の数十サンプルが毎フレーム固定される問題を調査しました。

調査結果をまとめると次のようになります。

  1. SPI DMA転送は正常だった
  2. FIFO全体のチェックサムは毎回変化していた
  3. 停止・再開を無効にしても先頭固定は発生した
  4. LFSRでは先頭から正常に変化した
  5. FIFO・SPI・DMA・アンパック処理は正常と判断できた
  6. PLLセトリング時間だけでは改善しなかった
  7. TR_STARTを増やすと症状は変化したが、固定は解消しなかった
  8. CCR3のT_INIT0が約50.5µsしかなかった
  9. データシートの最小要件は70µsだった
  10. T_INIT0を約100.9µsへ延長すると、先頭からデータが変化した

最終的な設定変更は次です。

// 変更前
0x5fbf3e1eUL,

// 変更後
0x5fff3e1eUL,

BGT60ATR24AIPでFIFO先頭データが固定される場合、SPIやDMAだけでなく、CCR3のT_INIT0とADCキャリブレーション時間を確認することが重要です。

KUMITATE

ESP32を、読むだけでなく
実際に動かして学びませんか?

クミタテは、組み込み開発を実機とオンライン教材で学べる 学習プラットフォームです。ESP32、電子回路、基板設計などの 教材を自分のペースで学習できます。

  • 無料で閲覧できる学習コンテンツあり
  • 実際に手を動かせるサンプルを掲載
  • Googleアカウントで登録可能
クミタテに無料登録する
スポンサーリンク