FMCW(連続波)レーダーの原理と実装を解説

FMCW(Frequency Modulated Continuous Wave)レーダーは、周波数変調された連続波を送信し、送信信号と受信信号の周波数差(ビート周波数)から目標までの距離や速度を測定するレーダーです。パルスレーダーとは異なり、連続的に信号を送受信するため、低い送信電力でも高い平均電力を得ることができます。

FMCWレーダーは、車載レーダー(77GHz帯)、航空機用高度計、近距離の工業用センサなど、幅広い応用を持ちます。特に、自動車のADAS(先進運転支援システム)や自動運転において、77GHz帯FMCWレーダーは距離・速度・角度の同時計測が可能な中核センサとして不可欠な存在となっています。

本記事の内容

  • FMCWレーダーの動作原理(三角波変調とビート周波数)
  • ビート周波数と距離の関係の導出
  • 上りチャープと下りチャープによる距離・速度の同時測定
  • 距離分解能と速度分解能の導出
  • 2D-FFTによるRange-Velocity処理
  • FMCWレーダー方程式
  • Pythonによるシミュレーション

前提知識

この記事を読む前に、以下の記事を読んでおくと理解が深まります。

FMCWレーダーとは

FMCW(Frequency Modulated Continuous Wave)レーダーとは、送信周波数を時間的に連続変化(変調)させ、送信信号と受信信号をミキシング(乗算)して得られるビート信号の周波数から、目標の距離と速度を測定するレーダーです。

パルスレーダーが送信パルスの往復時間から距離を測定するのに対し、FMCWレーダーは周波数の差から距離情報を抽出します。これにより、パルスレーダーでは困難な近距離目標の検出が可能になります。

FMCWレーダーの動作原理

送信信号のモデル

FMCWレーダーの送信信号は、キャリア周波数 $f_c$ を中心として、帯域幅 $B$ にわたって線形に周波数を変化させるチャープ信号です。1回の掃引時間を $T_m$ とすると、上りチャープ(up-chirp)の瞬時周波数は次のように表されます。

$$ f_{\text{TX}}(t) = f_c + \frac{B}{T_m} t = f_c + \gamma t $$

ここで、$\gamma = B / T_m$ はチャープレート(周波数変化率)です。

送信信号の位相は瞬時周波数の積分で得られます。

$$ \begin{align} \phi_{\text{TX}}(t) &= 2\pi \int_0^t f_{\text{TX}}(\tau) \, d\tau \\ &= 2\pi \int_0^t \left(f_c + \gamma \tau\right) d\tau \\ &= 2\pi \left(f_c t + \frac{\gamma t^2}{2}\right) \end{align} $$

したがって、送信信号は次のように書けます。

$$ s_{\text{TX}}(t) = A_{\text{TX}} \exp\left[j 2\pi \left(f_c t + \frac{\gamma t^2}{2}\right)\right] $$

受信信号のモデル

距離 $R$ にある静止目標からの反射信号は、往復遅延 $\tau = 2R/c$ だけ遅れた送信信号のコピーです($c$ は光速)。

$$ s_{\text{RX}}(t) = A_{\text{RX}} \exp\left[j 2\pi \left(f_c (t – \tau) + \frac{\gamma (t – \tau)^2}{2}\right)\right] $$

ビート信号の生成

FMCWレーダーでは、送信信号と受信信号をミキサーで混合(共役乗算)してビート信号を生成します。

$$ s_b(t) = s_{\text{TX}}(t) \cdot s_{\text{RX}}^*(t) $$

位相差を計算します。

$$ \begin{align} \phi_b(t) &= \phi_{\text{TX}}(t) – \phi_{\text{RX}}(t) \\ &= 2\pi \left(f_c t + \frac{\gamma t^2}{2}\right) – 2\pi \left(f_c (t-\tau) + \frac{\gamma (t-\tau)^2}{2}\right) \\ &= 2\pi \left(f_c \tau + \frac{\gamma}{2}\left[t^2 – (t-\tau)^2\right]\right) \\ &= 2\pi \left(f_c \tau + \frac{\gamma}{2}\left[2t\tau – \tau^2\right]\right) \\ &= 2\pi \left(f_c \tau + \gamma t \tau – \frac{\gamma \tau^2}{2}\right) \end{align} $$

ここで、$\tau$ が小さい($\gamma\tau^2/2$ の項は無視できる)と仮定すると、

$$ \phi_b(t) \approx 2\pi \left(f_c \tau + \gamma \tau \cdot t\right) $$

ビート信号の瞬時周波数は、

$$ f_b = \frac{1}{2\pi}\frac{d\phi_b}{dt} = \gamma \tau = \frac{B}{T_m} \cdot \frac{2R}{c} $$

$$ \boxed{f_b = \frac{2BR}{cT_m}} $$

これがFMCWレーダーの基本関係式です。ビート周波数 $f_b$ は目標距離 $R$ に比例し、帯域幅 $B$ とチャープ時間 $T_m$ によって決まります。

距離の算出

ビート周波数から距離を求めるには、上式を $R$ について解きます。

$$ \boxed{R = \frac{c T_m f_b}{2B}} $$

距離と速度の同時測定

移動目標に対するビート周波数

目標が視線方向に速度 $v$ で接近している場合、往復遅延は時間とともに変化します。

$$ \tau(t) = \frac{2R(t)}{c} = \frac{2(R_0 – vt)}{c} $$

この場合のビート周波数を計算すると、ドップラー周波数 $f_D = 2v/\lambda$ が加わります。

上りチャープのビート周波数:

$$ f_{b,\text{up}} = \gamma \tau_0 + f_D = \frac{2BR_0}{cT_m} + \frac{2v}{\lambda} $$

下りチャープ(周波数が減少する区間)のビート周波数:

$$ f_{b,\text{down}} = -\gamma \tau_0 + f_D = -\frac{2BR_0}{cT_m} + \frac{2v}{\lambda} $$

ここで符号に注意すると、下りチャープでは周波数変化率が $-\gamma$ になるため、距離に起因する項の符号が反転します。ビート周波数の絶対値をとると、

$$ |f_{b,\text{up}}| = \frac{2BR_0}{cT_m} + \frac{2v}{\lambda} $$

$$ |f_{b,\text{down}}| = \frac{2BR_0}{cT_m} – \frac{2v}{\lambda} $$

($f_D < \gamma\tau_0$ の場合)

距離と速度の分離

上りチャープと下りチャープのビート周波数の和と差をとることで、距離と速度を分離できます。

距離の算出

$$ \begin{align} \frac{|f_{b,\text{up}}| + |f_{b,\text{down}}|}{2} &= \frac{2BR_0}{cT_m} \\ \therefore R_0 &= \frac{cT_m}{4B}\left(|f_{b,\text{up}}| + |f_{b,\text{down}}|\right) \end{align} $$

速度の算出

$$ \begin{align} \frac{|f_{b,\text{up}}| – |f_{b,\text{down}}|}{2} &= \frac{2v}{\lambda} = f_D \\ \therefore v &= \frac{\lambda}{4}\left(|f_{b,\text{up}}| – |f_{b,\text{down}}|\right) \end{align} $$

このように、三角波変調(上りチャープ+下りチャープ)を用いることで、距離と速度を同時に測定できます。

距離分解能と速度分解能

距離分解能

距離分解能は、2つの近接する目標を区別できる最小距離差です。FMCWレーダーでは、ビート信号をFFTすることで周波数分析を行います。1回のチャープ(時間 $T_m$)のFFTにおける周波数分解能は $\Delta f = 1/T_m$ です。

$f_b = 2BR/(cT_m)$ の関係から、周波数分解能 $\Delta f$ に対応する距離分解能は、

$$ \begin{align} \Delta R &= \frac{cT_m}{2B} \Delta f \\ &= \frac{cT_m}{2B} \cdot \frac{1}{T_m} \\ &= \frac{c}{2B} \end{align} $$

$$ \boxed{\Delta R = \frac{c}{2B}} $$

これはパルス圧縮レーダーの距離分解能と同じ式です。距離分解能は帯域幅 $B$ のみで決まり、チャープ時間 $T_m$ には依存しません。

例えば、77GHz帯車載レーダーで $B = 4\,\text{GHz}$ の場合、

$$ \Delta R = \frac{3 \times 10^8}{2 \times 4 \times 10^9} = 0.0375\,\text{m} \approx 3.75\,\text{cm} $$

速度分解能

速度分解能を導出するために、まず複数チャープを用いた速度測定の原理を説明します。

$N$ 回の連続チャープを送信する場合、$m$ 番目のチャープ($m = 0, 1, \ldots, N-1$)に対するビート信号の位相は次のように表されます。

$$ \phi_m = 2\pi f_c \cdot \frac{2R_0}{c} – 2\pi f_c \cdot \frac{2v \cdot m T_m}{c} = \phi_0 – 2\pi \frac{2v}{\lambda} m T_m $$

$m$ に沿ったFFT(ドップラーFFT)の周波数分解能は $1/(NT_m)$ であり、ドップラー周波数 $f_D = 2v/\lambda$ に対応する速度分解能は、

$$ \begin{align} \Delta f_D &= \frac{1}{N T_m} \\ \Delta v &= \frac{\lambda}{2} \Delta f_D = \frac{\lambda}{2 N T_m} \end{align} $$

$$ \boxed{\Delta v = \frac{\lambda}{2 N T_m}} $$

2D-FFTによるRange-Velocity処理

処理の全体像

現代のFMCWレーダーでは、三角波変調ではなく、同一方向のチャープ(鋸歯状波変調)を $N$ 回繰り返す方式が主流です。この場合、以下の2段階FFTで距離と速度を同時に推定します。

  1. Range FFT(高速時間軸のFFT): 各チャープのビート信号をFFTし、距離に対応する周波数スペクトルを得る
  2. Doppler FFT(低速時間軸のFFT): 各距離ビン(Range FFTの各周波数ビン)について、チャープ間の位相変化をFFTし、速度に対応するドップラー周波数を得る

数学的定式化

$m$ 番目のチャープ($m = 0, 1, \ldots, N-1$)の高速時間を $t$、各チャープ内のサンプル番号を $k$($k = 0, 1, \ldots, K-1$)とすると、ビート信号のサンプルは次のように表されます。

$$ s_b[m, k] = A \exp\left[j 2\pi \left(\frac{2BR_0}{cT_m} \cdot \frac{k}{f_s} – \frac{2v}{\lambda} m T_m\right)\right] $$

ここで、$f_s$ はADCのサンプリング周波数です。

Range FFT($k$ 方向のFFT)を適用すると、距離に対応するビンにピークが現れます。Doppler FFT($m$ 方向のFFT)を適用すると、速度に対応するビンにピークが現れます。これらを組み合わせた2D-FFTの結果は、Range-Velocityマップと呼ばれます。

FMCWレーダー方程式

パルスレーダーのレーダー方程式と同様に、FMCWレーダーのSNRは次のように与えられます。

FMCWレーダーのビート信号のSNRは、コヒーレント積分を考慮すると、

$$ \text{SNR} = \frac{P_t G^2 \lambda^2 \sigma T_m}{(4\pi)^3 R^4 k_B T_0 F_n} $$

ここで、$P_t$ は送信電力、$G$ はアンテナ利得、$\sigma$ は目標のRCS、$k_B$ はボルツマン定数、$T_0$ はシステム雑音温度、$F_n$ はノイズ指数です。

パルスレーダーとの重要な違いは、$T_m$(チャープ時間)がSNRに入る点です。FMCWレーダーは連続送信であり、チャープ時間が長いほど積分時間が長くなるため、SNRが向上します。

$N$ チャープのコヒーレント積分を行う場合、SNRはさらに $N$ 倍に向上します。

$$ \text{SNR}_{\text{total}} = \frac{P_t G^2 \lambda^2 \sigma N T_m}{(4\pi)^3 R^4 k_B T_0 F_n} $$

車載レーダーへの応用

77GHz帯FMCWレーダー

自動車のADASや自動運転に使用される車載レーダーは、主に77GHz帯(76–81GHz)のFMCWレーダーです。主要なパラメータの例を示します。

パラメータ
キャリア周波数 $f_c$ 77 GHz
帯域幅 $B$ 4 GHz
チャープ時間 $T_m$ 40 μs
チャープ数 $N$ 256
距離分解能 $\Delta R$ 3.75 cm
最大検出距離 約200 m
速度分解能 $\Delta v$ 約0.19 m/s

77GHz帯を使用する理由は、波長が約3.9mmと短いため、小型のアンテナで高い角度分解能を得られ、車両への搭載が容易なためです。

Pythonによるシミュレーション

FMCW信号の生成とビート信号処理

import numpy as np
import matplotlib.pyplot as plt

# FMCWレーダーパラメータ
c = 3e8          # 光速 [m/s]
fc = 77e9        # キャリア周波数 [Hz]
B = 4e9          # 帯域幅 [Hz]
Tm = 40e-6       # チャープ時間 [s]
gamma = B / Tm   # チャープレート [Hz/s]
lam = c / fc     # 波長 [m]

# ADCパラメータ
fs = 20e6        # サンプリング周波数 [Hz]
N_samples = int(Tm * fs)  # 1チャープあたりのサンプル数

# 目標パラメータ(複数目標)
targets = [
    {'R': 30.0, 'v': 10.0, 'rcs': 10.0},   # 目標1: 30m, 10m/s
    {'R': 50.0, 'v': -5.0, 'rcs': 5.0},     # 目標2: 50m, -5m/s
    {'R': 80.0, 'v': 20.0, 'rcs': 2.0},     # 目標3: 80m, 20m/s
]

# チャープ数
N_chirps = 256

# ビート信号の生成(2Dデータ: N_chirps × N_samples)
beat_signal = np.zeros((N_chirps, N_samples), dtype=complex)
t_fast = np.arange(N_samples) / fs  # 高速時間

for m in range(N_chirps):
    for tgt in targets:
        R0 = tgt['R']
        v = tgt['v']
        rcs = tgt['rcs']
        # m番目のチャープ時点での距離
        R_m = R0 - v * m * Tm
        tau = 2 * R_m / c
        # ビート周波数
        fb = 2 * B * R_m / (c * Tm)
        # ドップラー位相
        doppler_phase = -2 * np.pi * 2 * v / lam * m * Tm
        # ビート信号
        amplitude = np.sqrt(rcs)
        beat_signal[m, :] += amplitude * np.exp(
            1j * 2 * np.pi * fb * t_fast + 1j * doppler_phase
        )

# 雑音の追加
noise_power = 0.01
beat_signal += np.sqrt(noise_power / 2) * (
    np.random.randn(N_chirps, N_samples) +
    1j * np.random.randn(N_chirps, N_samples)
)

print(f"ビート信号サイズ: {beat_signal.shape}")
print(f"距離分解能: {c / (2 * B):.3f} m")
print(f"速度分解能: {lam / (2 * N_chirps * Tm):.3f} m/s")

Range FFTとDoppler FFT(2D-FFT)

import numpy as np
import matplotlib.pyplot as plt

# (上のコードの続き — パラメータと信号生成を再掲)
c = 3e8
fc = 77e9
B = 4e9
Tm = 40e-6
gamma = B / Tm
lam = c / fc
fs = 20e6
N_samples = int(Tm * fs)
N_chirps = 256

targets = [
    {'R': 30.0, 'v': 10.0, 'rcs': 10.0},
    {'R': 50.0, 'v': -5.0, 'rcs': 5.0},
    {'R': 80.0, 'v': 20.0, 'rcs': 2.0},
]

beat_signal = np.zeros((N_chirps, N_samples), dtype=complex)
t_fast = np.arange(N_samples) / fs

for m in range(N_chirps):
    for tgt in targets:
        R0, v, rcs = tgt['R'], tgt['v'], tgt['rcs']
        R_m = R0 - v * m * Tm
        fb = 2 * B * R_m / (c * Tm)
        doppler_phase = -2 * np.pi * 2 * v / lam * m * Tm
        beat_signal[m, :] += np.sqrt(rcs) * np.exp(
            1j * 2 * np.pi * fb * t_fast + 1j * doppler_phase
        )

noise_power = 0.01
beat_signal += np.sqrt(noise_power / 2) * (
    np.random.randn(N_chirps, N_samples) +
    1j * np.random.randn(N_chirps, N_samples)
)

# ---- 2D-FFT処理 ----
# 1. Range FFT(高速時間方向のFFT)
N_range = 1024  # ゼロパディング
range_fft = np.fft.fft(beat_signal, n=N_range, axis=1)

# 2. Doppler FFT(低速時間方向のFFT)
N_doppler = 512  # ゼロパディング
range_doppler = np.fft.fftshift(
    np.fft.fft(range_fft, n=N_doppler, axis=0), axes=0
)

# 軸の計算
range_axis = np.arange(N_range) * c * Tm * fs / (2 * B * N_range)
# ドップラー周波数軸
doppler_axis = np.fft.fftshift(np.fft.fftfreq(N_doppler, d=Tm))
velocity_axis = doppler_axis * lam / 2  # ドップラー → 速度

# Range-Velocityマップの表示
RD_map = 20 * np.log10(np.abs(range_doppler) + 1e-12)
RD_map -= RD_map.max()  # 正規化

plt.figure(figsize=(12, 7))
extent = [range_axis[0], range_axis[N_range//2],
          velocity_axis[0], velocity_axis[-1]]
plt.imshow(
    RD_map[:, :N_range//2],
    aspect='auto',
    extent=extent,
    origin='lower',
    cmap='jet',
    vmin=-40, vmax=0
)
plt.colorbar(label='Normalized Power [dB]')
plt.xlabel('Range [m]')
plt.ylabel('Velocity [m/s]')
plt.title('Range-Velocity Map (2D-FFT)')

# 目標位置をマーク
for tgt in targets:
    plt.plot(tgt['R'], tgt['v'], 'w+', markersize=15, markeredgewidth=2)
    plt.annotate(f"R={tgt['R']}m, v={tgt['v']}m/s",
                 (tgt['R'] + 2, tgt['v'] + 1),
                 color='white', fontsize=10)

plt.tight_layout()
plt.show()

このシミュレーションでは、3つの目標(距離30m・速度10m/s、距離50m・速度-5m/s、距離80m・速度20m/s)に対する2D-FFTの結果をRange-Velocityマップとして表示しています。各目標の位置に明確なピークが現れていることが確認できます。

ビート信号のスペクトル可視化

import numpy as np
import matplotlib.pyplot as plt

# パラメータ再設定
c = 3e8
fc = 77e9
B = 4e9
Tm = 40e-6
gamma = B / Tm
lam = c / fc
fs = 20e6
N_samples = int(Tm * fs)

# 単一チャープ・単一目標でのビート信号
R_target = 50.0  # 距離 50m
tau = 2 * R_target / c
fb_expected = 2 * B * R_target / (c * Tm)

t = np.arange(N_samples) / fs
beat = np.exp(1j * 2 * np.pi * fb_expected * t)
beat += 0.1 * (np.random.randn(N_samples) + 1j * np.random.randn(N_samples))

# FFT
N_fft = 4096
spectrum = np.fft.fft(beat, n=N_fft)
freq_axis = np.fft.fftfreq(N_fft, d=1/fs)
range_axis_fft = freq_axis * c * Tm / (2 * B)

# 正の周波数のみ表示
pos_mask = freq_axis >= 0
spectrum_dB = 20 * np.log10(np.abs(spectrum[pos_mask]) + 1e-12)
spectrum_dB -= spectrum_dB.max()

fig, axes = plt.subplots(2, 1, figsize=(12, 8))

# 上段: ビート信号の時間波形
axes[0].plot(t * 1e6, np.real(beat), 'b-', linewidth=0.5)
axes[0].set_xlabel('Time [μs]')
axes[0].set_ylabel('Amplitude')
axes[0].set_title('Beat Signal (Time Domain)')
axes[0].set_xlim([0, 5])  # 最初の5μsを表示
axes[0].grid(True, alpha=0.3)

# 下段: スペクトル(距離軸)
axes[1].plot(range_axis_fft[pos_mask], spectrum_dB, 'r-', linewidth=1.0)
axes[1].axvline(x=R_target, color='g', linestyle='--',
                label=f'True range = {R_target} m')
axes[1].set_xlabel('Range [m]')
axes[1].set_ylabel('Power [dB]')
axes[1].set_title(f'Range Spectrum (Expected beat freq = {fb_expected/1e3:.1f} kHz)')
axes[1].set_xlim([0, 150])
axes[1].set_ylim([-60, 5])
axes[1].legend(fontsize=12)
axes[1].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

print(f"期待されるビート周波数: {fb_expected/1e3:.1f} kHz")
print(f"期待される距離: {R_target} m")

距離分解能の検証

2つの近接する目標を分離できるかどうかを帯域幅を変えて検証します。

import numpy as np
import matplotlib.pyplot as plt

c = 3e8
fc = 77e9
Tm = 40e-6
fs = 20e6
N_samples = int(Tm * fs)
N_fft = 8192

# 2つの近接目標
R1 = 50.0  # 目標1
R2 = 50.1  # 目標2(10cm離れている)

# 異なる帯域幅での分解能を比較
bandwidths = [1e9, 2e9, 4e9]  # 1GHz, 2GHz, 4GHz

fig, axes = plt.subplots(1, 3, figsize=(16, 5))

for idx, B in enumerate(bandwidths):
    gamma_val = B / Tm
    delta_R = c / (2 * B)  # 距離分解能

    # ビート信号(2目標)
    t = np.arange(N_samples) / fs
    fb1 = 2 * B * R1 / (c * Tm)
    fb2 = 2 * B * R2 / (c * Tm)
    beat = np.exp(1j * 2 * np.pi * fb1 * t) + np.exp(1j * 2 * np.pi * fb2 * t)

    # FFT
    spectrum = np.fft.fft(beat, n=N_fft)
    freq_axis = np.fft.fftfreq(N_fft, d=1/fs)
    range_axis = freq_axis * c * Tm / (2 * B)

    pos_mask = freq_axis >= 0
    spec_dB = 20 * np.log10(np.abs(spectrum[pos_mask]) + 1e-12)
    spec_dB -= spec_dB.max()

    axes[idx].plot(range_axis[pos_mask], spec_dB, 'b-', linewidth=1.0)
    axes[idx].axvline(x=R1, color='r', linestyle='--', alpha=0.7, label=f'R1={R1}m')
    axes[idx].axvline(x=R2, color='g', linestyle='--', alpha=0.7, label=f'R2={R2}m')
    axes[idx].set_xlabel('Range [m]')
    axes[idx].set_ylabel('Power [dB]')
    axes[idx].set_title(f'B={B/1e9:.0f}GHz, ΔR={delta_R*100:.1f}cm')
    axes[idx].set_xlim([49, 51.5])
    axes[idx].set_ylim([-30, 5])
    axes[idx].legend()
    axes[idx].grid(True, alpha=0.3)

plt.suptitle('Range Resolution vs Bandwidth', fontsize=14, y=1.02)
plt.tight_layout()
plt.show()

帯域幅が広いほど距離分解能が向上し、10cm離れた2つの目標を分離できるようになることが確認できます。$B = 4\,\text{GHz}$ では $\Delta R = 3.75\,\text{cm}$ なので、10cm離れた目標は十分に分離可能です。

まとめ

本記事では、FMCWレーダーの原理と実装について解説しました。

  • FMCWレーダーは周波数変調した連続波を送信し、ビート周波数 $f_b = 2BR/(cT_m)$ から距離を測定する
  • 三角波変調(上り/下りチャープ)のビート周波数の和と差から、距離と速度を同時に分離できる
  • 距離分解能は $\Delta R = c/(2B)$ で帯域幅のみに依存し、速度分解能は $\Delta v = \lambda/(2NT_m)$ でチャープ数と時間に依存する
  • 現代のFMCWレーダーでは、2D-FFT(Range FFT + Doppler FFT)によるRange-Velocity処理が標準的に用いられる
  • 77GHz帯の車載FMCWレーダーは、4GHz帯域幅で約3.75cmの距離分解能を実現できる

次のステップとして、以下の記事も参考にしてください。