ASK/FSK変調の理論 — デジタル振幅・周波数変調の原理と実装

テレビのリモコンのボタンを押すと、赤外線 LED が点滅して信号を送ります。ボタンを押している間は光が出て、離すと消える。この「光のオン・オフ」こそ、最も素朴なデジタル変調 — OOK(On-Off Keying) です。では、光の代わりに電波を使い、もっと複雑な情報を送りたい場合はどうすればよいでしょうか?

デジタル通信では、0と1のビット列を電波に乗せるために 変調(modulation) が不可欠です。アナログの世界では搬送波の振幅を連続的に変える AM(振幅変調)や、周波数を連続的に変える FM(周波数変調)がおなじみですが、デジタルの世界ではこれらを 離散的な値 で切り替えます。振幅を離散的に切り替えるのが ASK(Amplitude Shift Keying:振幅偏移変調)、周波数を離散的に切り替えるのが FSK(Frequency Shift Keying:周波数偏移変調) です。

ASK と FSK を理解すると、以下のような幅広い応用に直結します。

  • RFIDタグや近距離無線通信(NFC) — 低コスト・低消費電力のタグ通信では ASK/OOK が広く使われています
  • Bluetooth Low Energy(BLE) — GFSK(ガウスFSK)が物理層で採用されています
  • LoRa/LoRaWAN — CSS(Chirp Spread Spectrum)の基盤は FSK の拡張です
  • 航空無線のACARSや船舶のAIS — 伝統的な FSK ベースのデータリンクが現役です
  • PSK/QAMへの理解の橋渡し — ASK と FSK の限界を知ることで、より高度な変調方式の必要性が見えてきます

本記事の内容

  • ASK(振幅偏移変調)の原理と数学的記述 — OOK, M-ASK
  • FSK(周波数偏移変調)の原理と数学的記述 — BFSK, M-FSK, MSK
  • 各方式の帯域幅効率とBER特性の理論的比較
  • Pythonによる ASK/FSK 変調・復調シミュレーションとスペクトル可視化

前提知識

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

ASK(振幅偏移変調)とは

直感的理解

ASK の考え方は非常にシンプルです。懐中電灯を使ってモールス信号を送る場面を想像してください。光を強く照らしたら「1」、弱く照らしたら「0」と約束する。受け手は光の明るさ(振幅)を見て、送られたビットを判断します。

電波の世界でも同じです。搬送波(一定周波数の正弦波)の 振幅 をデジタルデータに応じて切り替えます。振幅の段階が2つなら 2-ASK(バイナリASK)、$M$ 段階なら M-ASK です。特に、振幅を「オン」と「オフ」の2段階にする方式を OOK(On-Off Keying) と呼びます。

OOK の数学的記述

OOK は最もシンプルな ASK であり、送信ビット $b[n] \in \{0, 1\}$ に対して、第 $n$ シンボル区間 $nT_s \leq t < (n+1)T_s$ の送信信号は次のように表されます。

$$ s_{\text{OOK}}(t) = b[n] \cdot A \cos(2\pi f_c t) $$

ここで $A$ は搬送波の振幅、$f_c$ は搬送波周波数、$T_s$ はシンボル周期です。$b[n] = 1$ のとき搬送波が送信され、$b[n] = 0$ のとき信号はゼロになります。

この式が表していることを直感的に言えば、「ビットが1なら搬送波を出す、0なら出さない」というだけです。実装の簡単さから、赤外線リモコンや RFID タグなど、コストと消費電力が重視される場面で広く採用されています。

バイナリASK(2-ASK)の数学的記述

OOK を一般化して、ビット値に応じて2つの異なる振幅 $A_0$ と $A_1$ を使う方式がバイナリASKです。

$$ s_{\text{2-ASK}}(t) = A_{b[n]} \cos(2\pi f_c t), \quad b[n] \in \{0, 1\} $$

通常は $A_0 = 0$, $A_1 = A$(OOK と同じ)、または $A_0 = -A$, $A_1 = A$(2-ASK の対称配置、実質的に BPSK と等価)が使われます。

対称配置の場合を $\{-1, +1\}$ のシンボル $a[n]$ で書き直すと

$$ s_{\text{2-ASK}}(t) = a[n] \cdot A \cos(2\pi f_c t), \quad a[n] \in \{-1, +1\} $$

となり、これは BPSK(Binary Phase Shift Keying)の式と完全に一致します。つまり、対称配置の 2-ASK と BPSK は数学的に同じ変調方式です。

M-ASK の数学的記述

より多くのビットを1シンボルで送るために、振幅の段階を $M$ 個に増やしたのが M-ASK です。$M = 2^m$ として、1シンボルあたり $m$ ビットを送ります。

シンボル値 $a[n] \in \{-(M-1), -(M-3), \dots, (M-3), (M-1)\}$ に対して、送信信号は

$$ s_{\text{M-ASK}}(t) = a[n] \cdot A \cos(2\pi f_c t) $$

と表されます。例えば $M = 4$(4-ASK)では $a[n] \in \{-3, -1, +1, +3\}$ の4段階で、1シンボルあたり2ビットを伝送します。

M-ASK の信号空間(コンスタレーション)は1次元の直線上に等間隔に $M$ 個の点が並ぶ形です。シンボル間の最小距離を $d_{\min}$ とすると

$$ d_{\min} = 2A $$

であり、平均シンボルエネルギーは

$$ E_s = A^2 \cdot \frac{1}{M} \sum_{i=0}^{M-1} (2i – M + 1)^2 = \frac{A^2 (M^2 – 1)}{3} $$

で与えられます。$M$ が大きくなると平均エネルギーが急増する一方、シンボル間距離は変わらないため、雑音耐性が劣化します。

ここまでで ASK の基本的な数学的構造を整理しました。ASK はシンプルで実装が容易ですが、振幅の変化は雑音やフェージングの影響を受けやすいという弱点があります。そこで、振幅ではなく 周波数 を切り替える方式が登場します。

FSK(周波数偏移変調)とは

直感的理解

FSK の考え方も日常的な体験から理解できます。救急車のサイレンを想像してください。近づいてくるときは高い音、遠ざかるときは低い音に聞こえます(ドップラー効果)。FSK はこれを人工的に行います — データに応じて搬送波の 周波数 を高くしたり低くしたりするのです。

ASK が「声の大きさ」を変えるのに対し、FSK は「声の高さ」を変えます。一般に、振幅の変動は伝搬路の影響(フェージング)を受けやすいですが、周波数はそれほど影響を受けません。この雑音耐性の高さが FSK の大きな利点です。

BFSK(バイナリFSK)の数学的記述

BFSK は最も基本的な FSK で、送信ビット $b[n] \in \{0, 1\}$ に対して2つの周波数 $f_0$ と $f_1$ を切り替えます。

$$ s_{\text{BFSK}}(t) = A \cos(2\pi f_{b[n]} t + \phi) $$

ここで $f_0$ と $f_1$ は搬送波周波数 $f_c$ を中心として

$$ f_0 = f_c – \frac{\Delta f}{2}, \quad f_1 = f_c + \frac{\Delta f}{2} $$

と配置されます。$\Delta f = f_1 – f_0$ を 周波数偏移(frequency deviation) と呼びます。

この式が意味していることは直感的です。ビットが0のときは低い周波数 $f_0$ の正弦波を、ビットが1のときは高い周波数 $f_1$ の正弦波を送ります。受信側は、受信信号の周波数を判別してビットを復元します。

位相連続性と変調指数

BFSK には、シンボル境界で位相が不連続になる 非連続位相FSK(discontinuous-phase FSK) と、位相が連続的につながる 連続位相FSK(CPFSK:Continuous Phase FSK) の2種類があります。

CPFSKの送信信号は次のように表されます。

$$ s_{\text{CPFSK}}(t) = A \cos\left(2\pi f_c t + 2\pi \Delta f \int_0^t m(\tau) \, d\tau + \phi_0\right) $$

ここで $m(\tau)$ はベースバンドの変調信号($\{-1/2, +1/2\}$ の矩形パルス列)、$\phi_0$ は初期位相です。積分の効果により、周波数の切り替わりが位相の連続的な変化として表現されます。

変調指数(modulation index) $h$ は、FSK の最も重要なパラメータの1つです。

$$ h = \frac{\Delta f}{R_s} = \Delta f \cdot T_s $$

ここで $R_s = 1/T_s$ はシンボルレートです。$h$ は「1シンボル区間で2つの周波数の位相差がどれだけ蓄積するか」を表し、具体的には

$$ \Delta \phi = 2\pi h \cdot \frac{1}{2} = \pi h $$

だけ位相差が生じます($m(\tau) = +1/2$ と $m(\tau) = -1/2$ の位相差)。

$h$ の値によって FSK の性質が大きく変わります。

  • $h = 1$: Sunde の FSK — 直交条件を満たす最小の整数変調指数
  • $h = 0.5$: MSK(Minimum Shift Keying) — 帯域効率が最も優れた CPFSK
  • $h > 1$: 広帯域FSK — BER 性能は向上するが帯域幅が増大

ここで重要な問いが生じます — 2つの FSK 信号が「直交する」とはどういうことでしょうか。次にこの条件を数学的に導きます。

FSK の直交条件

2つの正弦波信号 $s_0(t) = A\cos(2\pi f_0 t)$ と $s_1(t) = A\cos(2\pi f_1 t)$ が直交するための条件は、1シンボル区間 $[0, T_s]$ での内積がゼロになることです。

$$ \int_0^{T_s} s_0(t) \, s_1(t) \, dt = \frac{A^2}{2} \int_0^{T_s} \cos(2\pi (f_1 – f_0) t) \, dt + \frac{A^2}{2} \int_0^{T_s} \cos(2\pi (f_1 + f_0) t) \, dt $$

右辺の第2項(和の周波数成分)は $f_0 + f_1$ が十分大きければ、$T_s$ に対して急速に振動してほぼゼロに近づきます。第1項に注目すると、$\Delta f = f_1 – f_0$ として

$$ \frac{A^2}{2} \int_0^{T_s} \cos(2\pi \Delta f \, t) \, dt = \frac{A^2}{2} \cdot \frac{\sin(2\pi \Delta f \, T_s)}{2\pi \Delta f} $$

これがゼロになる条件は

$$ \sin(2\pi \Delta f \, T_s) = 0 \quad \Longrightarrow \quad \Delta f \, T_s = \frac{k}{2}, \quad k = 1, 2, 3, \dots $$

すなわち $h = \Delta f \cdot T_s = k/2$($k$ は正の整数)です。最小の直交変調指数は $h = 1/2$ であり、これが MSK に対応します。

非連続位相FSKの場合は $h = 1$($k = 2$)が最小直交条件ですが、CPFSK では $h = 1/2$ でも直交が成り立ちます。この差は、連続位相の構造が追加の情報を持つためです。

M-FSK の数学的記述

$M$ 個の周波数を使って1シンボルあたり $m = \log_2 M$ ビットを送る方式が M-FSK です。

$$ s_i(t) = A \cos(2\pi f_i t), \quad i = 0, 1, \dots, M-1 $$

各周波数は

$$ f_i = f_c + \left(i – \frac{M-1}{2}\right) \Delta f $$

と配置されます。直交条件 $\Delta f = 1/(2T_s)$ を用いると、M-FSK の信号は $M$ 次元信号空間の直交軸上にそれぞれ配置されます。

M-FSK の信号空間は M-ASK とは根本的に異なります。M-ASK のコンスタレーションが1次元の直線上にあるのに対し、M-FSK は $M$ 次元空間 の各軸方向に1つずつシンボルを配置します。各シンボル間のユークリッド距離は全て等しく

$$ d_{ij} = A\sqrt{2T_s}, \quad i \neq j $$

です。この等距離性は、M-ASK の「隣接シンボルが近い」問題を回避しますが、代わりに信号空間の次元数(= 帯域幅)が $M$ とともに増大するという代償があります。

MSK(Minimum Shift Keying)

MSK は $h = 1/2$ の CPFSK であり、帯域効率とBER性能の間で優れたバランスを持つ変調方式です。MSK の信号は次のように表されます。

$$ s_{\text{MSK}}(t) = A \cos\left(2\pi f_c t + \frac{\pi a[n]}{2T_s}(t – nT_s) + \phi_n\right) $$

ここで $a[n] \in \{-1, +1\}$ はデータシンボル、$\phi_n$ は第 $n$ シンボル開始時の位相(前のシンボルからの連続性で決まる)です。

MSK の重要な特徴は、同相成分(I成分)と直交成分(Q成分)に分解できることです。

$$ s_{\text{MSK}}(t) = I(t) \cos(2\pi f_c t) – Q(t) \sin(2\pi f_c t) $$

ここで

$$ I(t) = a_I[n] \cos\left(\frac{\pi t}{2T_s}\right), \quad Q(t) = a_Q[n] \sin\left(\frac{\pi t}{2T_s}\right) $$

です。$a_I[n]$ と $a_Q[n]$ は偶数番目と奇数番目のビットから交互に取り出されるシンボルです。

この I/Q 分解から、MSK は以下の性質を持つことがわかります。

  • 包絡線が一定: $|I(t)|^2 + |Q(t)|^2$ は一定値を取ります。これは非線形増幅器(電力効率の高い C 級アンプ等)との相性が良いことを意味します
  • 帯域幅が BPSK/QPSK より狭い: 半正弦波のパルス整形により、サイドローブの減衰が速い
  • OQPSK(Offset QPSK)との関係: MSK は、パルス整形に半正弦波を使った OQPSK と等価です

MSK をさらにガウスフィルタで平滑化したものが GMSK(Gaussian MSK) であり、GSM(第2世代携帯電話)の変調方式として世界的に普及しました。

ここまでで ASK と FSK の基本的な数学的構造を整理しました。次に、これらの方式の帯域幅効率とBER性能を理論的に比較し、各方式の適用範囲を明らかにしましょう。

帯域幅効率の比較

帯域幅の定義

変調方式の帯域幅を議論するには、「帯域幅」の定義を明確にする必要があります。主に以下の3つの定義が使われます。

  • ヌル間帯域幅(Null-to-Null Bandwidth): パワースペクトル密度の最初のヌル(ゼロ点)間の幅。最も簡易的な指標
  • 99%帯域幅: 全電力の99%が含まれる帯域幅。実用的な指標
  • 3dB帯域幅: スペクトルのピークから3dB落ちた点の幅。狭帯域信号の指標

以下では、最も広く使われるヌル間帯域幅で比較します。

各方式の帯域幅

矩形パルスを用いた場合の各変調方式のヌル間帯域幅を整理します。ビットレートを $R_b$、シンボルレートを $R_s = R_b / \log_2 M$ とします。

ASK の帯域幅: ASK のベースバンド信号は矩形パルス列であり、そのパワースペクトル密度は sinc 関数の二乗に比例します。ヌル間帯域幅は

$$ B_{\text{ASK}} = 2R_s = \frac{2R_b}{\log_2 M} $$

です。$M$ が大きくなるほどシンボルレートが下がり、帯域幅は狭くなります。

FSK の帯域幅: FSK では、各周波数の信号が ASK 的な帯域幅を持つことに加えて、周波数間の分離に必要な幅が加わります。直交 BFSK($h = 1$)のヌル間帯域幅は

$$ B_{\text{BFSK}} = 2R_b + \Delta f = 2R_b + R_b = 3R_b $$

です。一般に、直交 M-FSK($h = 1$)では

$$ B_{\text{M-FSK}} = (M + 1) R_s = \frac{(M+1) R_b}{\log_2 M} $$

となります。$M$ が増えると分子が線形に増大するため、帯域幅は $M$ とともに増加します。

MSK の帯域幅: MSK($h = 0.5$)は半正弦波パルス整形の効果で、メインローブが狭く、サイドローブの減衰が速くなります。ヌル間帯域幅は

$$ B_{\text{MSK}} = 1.5 R_b $$

です。これは BPSK の $B = 2R_b$ よりも狭く、帯域効率に優れています。

帯域効率の比較表

帯域効率 $\eta = R_b / B$(ビット/秒/Hz)で各方式を比較します。

変調方式 ヌル間帯域幅 $B$ 帯域効率 $\eta$ [bit/s/Hz]
OOK / 2-ASK $2R_b$ 0.5
4-ASK $R_b$ 1.0
8-ASK $2R_b/3$ 1.5
BFSK ($h=1$) $3R_b$ 0.33
4-FSK ($h=1$) $2.5R_b$ 0.4
MSK ($h=0.5$) $1.5R_b$ 0.67
BPSK $2R_b$ 0.5
QPSK $R_b$ 1.0

この表から、帯域効率の観点では M-ASK が有利で、M-FSK は不利であることが読み取れます。FSK は $M$ を増やすと帯域幅が増大するため、帯域が限られた通信路には向きません。一方、ASK は $M$ を増やすと帯域効率が向上しますが、雑音耐性が劣化するトレードオフがあります。

では、雑音環境での実際の性能(BER)はどうなるのでしょうか。次に BER 特性を理論的に導出・比較します。

BER特性の理論解析

AWGN通信路のモデル

BER の解析では、加法性白色ガウス雑音(AWGN)通信路を仮定します。受信信号は

$$ r(t) = s(t) + n(t) $$

です。ここで $s(t)$ は送信信号、$n(t)$ は電力スペクトル密度 $N_0/2$ の AWGN です。受信側は 整合フィルタ(matched filter) またはそれと等価な 相関器 を使って最適検波を行います。

BER を比較する際の共通尺度として、ビットあたりのエネルギー対雑音電力密度比 $E_b/N_0$ を使います。

OOK の BER

OOK の最適検波における BER を導出します。OOK は $s_0(t) = 0$(ビット0)と $s_1(t) = A\cos(2\pi f_c t)$(ビット1)の2つの信号を使います。

2つの信号間のユークリッド距離は $d = \sqrt{E_s}$ です。ここで $E_s = A^2 T_s / 2$ はビット1の信号エネルギーです。平均ビットエネルギーは $E_b = E_s / 2$(ビット0のエネルギーはゼロ)です。

OOK のビット0とビット1で信号エネルギーが異なるため、最適閾値は $A/2$ となります。コヒーレント検波の場合、最適検波器の出力の条件付き確率分布は

$$ z | b = 0 \sim \mathcal{N}(0, N_0/2), \quad z | b = 1 \sim \mathcal{N}(\sqrt{E_s}, N_0/2) $$

です。ビット0を1と誤る確率とビット1を0と誤る確率を平均すると、BER は

$$ P_b^{\text{OOK}} = Q\left(\sqrt{\frac{E_s}{N_0}}\right) = Q\left(\sqrt{\frac{2E_b}{N_0}}\right) = \frac{1}{2}\text{erfc}\left(\sqrt{\frac{E_b}{N_0}}\right) $$

となります。ここで $Q(x) = \frac{1}{2}\text{erfc}(x/\sqrt{2})$ はQ関数です。

これを BPSK の BER $P_b^{\text{BPSK}} = Q(\sqrt{2E_b/N_0})$ と比較すると、OOK は BPSK に対して約 3 dB の劣化があります。この差は、OOK がビット0でエネルギーを送信しない(信号空間の原点が使われる)ため、平均エネルギーに対する信号間距離の比が悪いことに起因します。

コヒーレント BFSK の BER

直交 BFSK のコヒーレント検波における BER を導出します。2つの信号 $s_0(t)$ と $s_1(t)$ は直交しているため、信号空間は2次元です。各信号のエネルギーは等しく $E_s = E_b$ です。

2つの直交信号間のユークリッド距離は

$$ d = \sqrt{|s_1 – s_0|^2} = \sqrt{E_s + E_s} = \sqrt{2E_s} = \sqrt{2E_b} $$

です。$\sqrt{E_s}$ ずつの2軸方向の成分を考えると、距離が $\sqrt{2E_s}$ になるのは、2つの信号点が直交する2軸上にそれぞれ配置されるためです。

コヒーレント検波では、2つの相関器出力の差を判定変数とします。BER は

$$ P_b^{\text{BFSK}} = Q\left(\sqrt{\frac{E_b}{N_0}}\right) = \frac{1}{2}\text{erfc}\left(\sqrt{\frac{E_b}{2N_0}}\right) $$

となります。BPSK に対して約 3 dB の劣化があります。これは、直交信号の信号間距離が対蹠信号($\pm s$)の距離より短いためです。

非コヒーレント検波

実装の簡易さから、ASK/FSK では搬送波の位相情報を使わない 非コヒーレント検波 もよく用いられます。

非コヒーレント OOK の場合、包絡線検波器を使います。BER は

$$ P_b^{\text{OOK, nc}} = \frac{1}{2}\exp\left(-\frac{E_b}{2N_0}\right) $$

です。

非コヒーレント BFSK の場合、2つの包絡線検波器の出力を比較します。BER は

$$ P_b^{\text{BFSK, nc}} = \frac{1}{2}\exp\left(-\frac{E_b}{2N_0}\right) $$

です。非コヒーレント OOK と非コヒーレント BFSK の BER が同じ式になるのは興味深い結果です。どちらもコヒーレント検波に比べて約 1 dB の劣化があります。

M-ASK の BER

M-ASK のシンボル誤り率(SER)は、隣接シンボル間のユークリッド距離と雑音の分散で決まります。Gray符号化を仮定すると、隣接シンボルの誤りが1ビット誤りに対応するため

$$ P_b^{\text{M-ASK}} \approx \frac{2(M-1)}{M \log_2 M} Q\left(\sqrt{\frac{6 \log_2 M}{M^2 – 1} \cdot \frac{E_b}{N_0}}\right) $$

$M$ が大きくなると、$Q$ 関数の引数内の $\frac{6\log_2 M}{M^2 – 1}$ が急速に小さくなるため、同じ BER を達成するために必要な $E_b/N_0$ が大幅に増加します。

M-FSK の BER

M-FSK のコヒーレント検波における BER は、ユニオンバウンドにより近似できます。

$$ P_b^{\text{M-FSK}} \approx \frac{M/2}{M-1} Q\left(\sqrt{\frac{E_b \log_2 M}{N_0}}\right) $$

M-ASK とは対照的に、M-FSK では $M$ を増やすと $Q$ 関数の引数内の $\log_2 M$ が増大するため、BER が 改善 されます。これは M-FSK の信号空間の次元が $M$ とともに増大し、雑音の影響を受けにくくなるためです。

BER 特性の比較まとめ

変調方式 コヒーレント BER 非コヒーレント BER
OOK $Q(\sqrt{2E_b/N_0})$ $\frac{1}{2}\exp(-E_b/2N_0)$
BFSK $Q(\sqrt{E_b/N_0})$ $\frac{1}{2}\exp(-E_b/2N_0)$
BPSK $Q(\sqrt{2E_b/N_0})$
MSK $Q(\sqrt{2E_b/N_0})$

MSK の BER は BPSK と同じです。これは MSK が OQPSK の一種であり、同じビットエネルギー効率を持つためです。帯域幅はBPSKより狭い上に、BER性能も同等 — MSK が優れた変調方式と評価される理由がここにあります。

理論的な比較が終わったところで、Python シミュレーションでこれらの結果を実際に確認しましょう。

Pythonによる ASK 変調・復調シミュレーション

OOK 変調の波形とスペクトル

まず、OOK 変調の時間波形とパワースペクトルを可視化して、ASK の基本動作を確認します。

import numpy as np
import matplotlib.pyplot as plt

# パラメータ設定
fc = 10.0        # 搬送波周波数 [Hz]
Rs = 1.0         # シンボルレート [symbol/s]
Ts = 1.0 / Rs    # シンボル周期 [s]
fs = 1000.0      # サンプリング周波数 [Hz]
N_bits = 10      # ビット数

# ランダムビット列
np.random.seed(42)
bits = np.random.randint(0, 2, N_bits)

# 時間軸
t_total = N_bits * Ts
t = np.arange(0, t_total, 1/fs)

# ベースバンド信号(矩形パルス列)
baseband = np.zeros(len(t))
for i, b in enumerate(bits):
    idx = (t >= i * Ts) & (t < (i + 1) * Ts)
    baseband[idx] = b

# OOK変調信号
carrier = np.cos(2 * np.pi * fc * t)
ook_signal = baseband * carrier

# パワースペクトル密度
from numpy.fft import fft, fftfreq
N_fft = len(ook_signal)
freq = fftfreq(N_fft, 1/fs)
spectrum = np.abs(fft(ook_signal)) ** 2 / N_fft

fig, axes = plt.subplots(3, 1, figsize=(12, 9))

# ビット列とベースバンド
axes[0].step(np.arange(N_bits), bits, where='mid', linewidth=2, color='tab:blue')
axes[0].set_ylabel('Bit value')
axes[0].set_title('Data Bits')
axes[0].set_xlim(-0.5, N_bits - 0.5)
axes[0].set_ylim(-0.2, 1.4)
axes[0].grid(True, alpha=0.3)
for i, b in enumerate(bits):
    axes[0].text(i, b + 0.1, str(b), ha='center', fontsize=10)

# OOK変調波形
axes[1].plot(t, ook_signal, linewidth=0.8, color='tab:orange')
axes[1].set_ylabel('Amplitude')
axes[1].set_title('OOK Modulated Signal')
axes[1].set_xlim(0, t_total)
axes[1].grid(True, alpha=0.3)

# パワースペクトル
mask = (freq >= 0) & (freq <= 30)
axes[2].plot(freq[mask], 10 * np.log10(spectrum[mask] / np.max(spectrum[mask]) + 1e-12),
             linewidth=1, color='tab:green')
axes[2].set_xlabel('Frequency [Hz]')
axes[2].set_ylabel('PSD [dB]')
axes[2].set_title('Power Spectral Density of OOK Signal')
axes[2].set_ylim(-60, 5)
axes[2].grid(True, alpha=0.3)
axes[2].axvline(x=fc, color='r', linestyle='--', alpha=0.5, label=f'fc = {fc} Hz')
axes[2].legend()

plt.tight_layout()
plt.savefig('ook_modulation.png', dpi=150, bbox_inches='tight')
plt.show()

上のグラフから、OOK 変調の動作が明確に確認できます。上段のビット列で「1」のシンボル区間では搬送波が出力され、「0」の区間では振幅がゼロになっています(中段)。パワースペクトル(下段)を見ると、搬送波周波数 $f_c = 10$ Hz を中心に sinc 関数の二乗形のスペクトルが広がっていることがわかります。ヌル間帯域幅は $2R_s = 2$ Hz であり、最初のヌルが $f_c \pm 1$ Hz に位置しています。

M-ASK のコンスタレーションと波形

次に、M-ASK($M = 2, 4, 8$)のコンスタレーション図と変調波形を比較します。

import numpy as np
import matplotlib.pyplot as plt

fig, axes = plt.subplots(2, 3, figsize=(15, 8))

fc = 10.0
fs = 1000.0
N_symbols = 8

for idx, M in enumerate([2, 4, 8]):
    m = int(np.log2(M))

    # コンスタレーション点
    constellation = np.arange(-(M-1), M, 2)  # {-(M-1), ..., -1, 1, ..., (M-1)}

    # ランダムシンボル
    np.random.seed(idx)
    symbol_indices = np.random.randint(0, M, N_symbols)
    symbols = constellation[symbol_indices]

    # コンスタレーション図
    ax = axes[0, idx]
    ax.scatter(constellation, np.zeros(M), s=100, c='tab:blue', zorder=3)
    for i, c in enumerate(constellation):
        bits_label = format(i, f'0{m}b')
        ax.annotate(bits_label, (c, 0.05), ha='center', fontsize=9)
    ax.axhline(y=0, color='gray', linewidth=0.5)
    ax.axvline(x=0, color='gray', linewidth=0.5)
    ax.set_xlim(-(M+1), M+1)
    ax.set_ylim(-0.5, 0.5)
    ax.set_title(f'{M}-ASK Constellation')
    ax.set_xlabel('In-phase')
    ax.grid(True, alpha=0.3)

    # 変調波形
    Ts = 1.0
    t_total = N_symbols * Ts
    t = np.arange(0, t_total, 1/fs)
    signal = np.zeros(len(t))
    for i, s in enumerate(symbols):
        mask = (t >= i * Ts) & (t < (i + 1) * Ts)
        signal[mask] = s * np.cos(2 * np.pi * fc * t[mask])

    ax = axes[1, idx]
    ax.plot(t, signal, linewidth=0.8, color='tab:orange')
    ax.set_xlabel('Time [s]')
    ax.set_ylabel('Amplitude')
    ax.set_title(f'{M}-ASK Modulated Signal')
    ax.set_xlim(0, t_total)
    ax.grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('m_ask_constellation.png', dpi=150, bbox_inches='tight')
plt.show()

この図から、M-ASK の多値化の本質が視覚的に理解できます。上段のコンスタレーション図を見ると、$M$ が増えるほど振幅の段階が密になり、隣接シンボル間の距離が小さくなることがわかります。2-ASK(実質BPSK)では $\pm 1$ の2点だけですが、8-ASK では $-7$ から $+7$ まで8段階に分かれ、信号点の間隔が相対的に狭くなっています。下段の変調波形では、搬送波の振幅が複数のレベルで変化する様子が確認できます。振幅の差が小さくなるほど雑音による誤りが起きやすくなることが、波形からも直感的に見て取れます。

Pythonによる FSK 変調・復調シミュレーション

BFSK 変調の波形とスペクトル

FSK 変調の動作を可視化します。BFSK では、ビットに応じて搬送波の周波数が切り替わる様子が観察できるはずです。

import numpy as np
import matplotlib.pyplot as plt

# パラメータ設定
fc = 10.0         # 搬送波中心周波数 [Hz]
delta_f = 2.0     # 周波数偏移 [Hz](h = delta_f * Ts = 2.0)
f0 = fc - delta_f / 2  # ビット0の周波数
f1 = fc + delta_f / 2  # ビット1の周波数
Rs = 1.0
Ts = 1.0 / Rs
fs = 1000.0
N_bits = 10

# ランダムビット列
np.random.seed(42)
bits = np.random.randint(0, 2, N_bits)

# 時間軸
t_total = N_bits * Ts
t = np.arange(0, t_total, 1/fs)

# 非連続位相FSK
fsk_signal = np.zeros(len(t))
for i, b in enumerate(bits):
    mask = (t >= i * Ts) & (t < (i + 1) * Ts)
    freq = f1 if b == 1 else f0
    fsk_signal[mask] = np.cos(2 * np.pi * freq * t[mask])

# CPFSK(連続位相)
cpfsk_signal = np.zeros(len(t))
phase = 0.0
dt = 1.0 / fs
for i in range(len(t)):
    symbol_idx = min(int(t[i] / Ts), N_bits - 1)
    freq = f1 if bits[symbol_idx] == 1 else f0
    cpfsk_signal[i] = np.cos(2 * np.pi * fc * t[i] + phase)
    phase += 2 * np.pi * (freq - fc) * dt

# パワースペクトル
from numpy.fft import fft, fftfreq
N_fft = len(fsk_signal)
freq_axis = fftfreq(N_fft, 1/fs)
spectrum_disc = np.abs(fft(fsk_signal)) ** 2 / N_fft
spectrum_cont = np.abs(fft(cpfsk_signal)) ** 2 / N_fft

fig, axes = plt.subplots(3, 1, figsize=(12, 10))

# ビット列
axes[0].step(np.arange(N_bits), bits, where='mid', linewidth=2, color='tab:blue')
axes[0].set_ylabel('Bit value')
axes[0].set_title('Data Bits')
axes[0].set_xlim(-0.5, N_bits - 0.5)
axes[0].set_ylim(-0.2, 1.4)
axes[0].grid(True, alpha=0.3)
for i, b in enumerate(bits):
    axes[0].text(i, b + 0.1, str(b), ha='center', fontsize=10)

# CPFSK波形
axes[1].plot(t, cpfsk_signal, linewidth=0.8, color='tab:orange')
axes[1].set_ylabel('Amplitude')
axes[1].set_title(f'CPFSK Signal (fc={fc} Hz, Δf={delta_f} Hz, h={delta_f*Ts:.1f})')
axes[1].set_xlim(0, t_total)
axes[1].grid(True, alpha=0.3)

# シンボル境界を描画
for i in range(1, N_bits):
    axes[1].axvline(x=i * Ts, color='gray', linestyle=':', alpha=0.5)

# パワースペクトル比較
mask = (freq_axis >= 0) & (freq_axis <= 25)
axes[2].plot(freq_axis[mask],
             10 * np.log10(spectrum_disc[mask] / np.max(spectrum_disc[mask]) + 1e-12),
             linewidth=1, color='tab:blue', label='Discontinuous FSK', alpha=0.7)
axes[2].plot(freq_axis[mask],
             10 * np.log10(spectrum_cont[mask] / np.max(spectrum_cont[mask]) + 1e-12),
             linewidth=1, color='tab:red', label='CPFSK', alpha=0.7)
axes[2].set_xlabel('Frequency [Hz]')
axes[2].set_ylabel('PSD [dB]')
axes[2].set_title('Power Spectral Density Comparison')
axes[2].set_ylim(-60, 5)
axes[2].grid(True, alpha=0.3)
axes[2].axvline(x=f0, color='green', linestyle='--', alpha=0.5, label=f'f0 = {f0} Hz')
axes[2].axvline(x=f1, color='purple', linestyle='--', alpha=0.5, label=f'f1 = {f1} Hz')
axes[2].legend()

plt.tight_layout()
plt.savefig('bfsk_modulation.png', dpi=150, bbox_inches='tight')
plt.show()

上のグラフから FSK 変調の動作が明確に確認できます。上段のビット列に対応して、中段の CPFSK 波形ではビット「0」のとき低い周波数 $f_0 = 9$ Hz で振動し、ビット「1」のとき高い周波数 $f_1 = 11$ Hz で振動しています。周波数の切り替わりにもかかわらず、CPFSK では位相が連続的につながっている点に注目してください。下段のスペクトルを見ると、$f_0$ と $f_1$ の付近にエネルギーが集中しており、CPFSK(赤)は非連続位相FSK(青)に比べてサイドローブが低く抑えられていることがわかります。これは位相連続性がスペクトルのコンパクト化に寄与することを示しています。

MSK 変調の波形と位相トラジェクトリ

MSK は帯域効率に優れた特別な CPFSK です。MSK の特徴的な位相トラジェクトリ(位相軌跡)を可視化します。

import numpy as np
import matplotlib.pyplot as plt

# パラメータ設定
fc = 10.0
Rs = 1.0
Ts = 1.0 / Rs
h = 0.5            # MSKの変調指数
delta_f = h * Rs   # 周波数偏移 = 0.5 Hz
fs = 1000.0
N_bits = 12

# ランダムビット列
np.random.seed(123)
bits = np.random.randint(0, 2, N_bits)
symbols = 2 * bits - 1  # {-1, +1}

# MSK信号の生成(CPFSK方式)
t_total = N_bits * Ts
t = np.arange(0, t_total, 1/fs)
dt = 1.0 / fs

msk_signal = np.zeros(len(t))
phase = np.zeros(len(t))
cum_phase = 0.0

for i in range(len(t)):
    symbol_idx = min(int(t[i] / Ts), N_bits - 1)
    a = symbols[symbol_idx]
    # 瞬時周波数 = fc + a * delta_f/2 ではなく fc + a * delta_f
    # h = 0.5 なので delta_f = 0.5, 瞬時周波数偏移 = +/-0.25 Hz
    freq_dev = a * delta_f / 2
    msk_signal[i] = np.cos(2 * np.pi * fc * t[i] + cum_phase)
    phase[i] = cum_phase
    cum_phase += 2 * np.pi * freq_dev * dt

# 位相をアンラップして正規化
phase_normalized = phase / np.pi

fig, axes = plt.subplots(3, 1, figsize=(12, 10))

# ビット列
axes[0].step(np.arange(N_bits), bits, where='mid', linewidth=2, color='tab:blue')
axes[0].set_ylabel('Bit value')
axes[0].set_title('Data Bits')
axes[0].set_xlim(-0.5, N_bits - 0.5)
axes[0].set_ylim(-0.2, 1.4)
axes[0].grid(True, alpha=0.3)

# MSK波形
axes[1].plot(t, msk_signal, linewidth=0.8, color='tab:orange')
axes[1].set_ylabel('Amplitude')
axes[1].set_title(f'MSK Signal (h=0.5, fc={fc} Hz)')
axes[1].set_xlim(0, t_total)
axes[1].grid(True, alpha=0.3)

# シンボル境界
for i in range(1, N_bits):
    axes[1].axvline(x=i * Ts, color='gray', linestyle=':', alpha=0.5)

# 位相トラジェクトリ
axes[2].plot(t, phase_normalized, linewidth=2, color='tab:red')
axes[2].set_xlabel('Time [s]')
axes[2].set_ylabel('Phase [x π rad]')
axes[2].set_title('MSK Phase Trajectory')
axes[2].set_xlim(0, t_total)
axes[2].grid(True, alpha=0.3)

# シンボル境界とπ/2の倍数のグリッド
for i in range(1, N_bits):
    axes[2].axvline(x=i * Ts, color='gray', linestyle=':', alpha=0.5)

plt.tight_layout()
plt.savefig('msk_modulation.png', dpi=150, bbox_inches='tight')
plt.show()

このグラフから MSK の特徴的な振る舞いが明確に観察できます。中段の波形を見ると、振幅が常に一定(包絡線一定)であることがわかります。これは非線形増幅器との相性の良さを意味します。下段の位相トラジェクトリ(位相の時間変化)は、各シンボル区間で $\pm \pi/2$ ずつ直線的に変化しています。これは変調指数 $h = 0.5$ のCPFSKの特性で、1シンボルあたりの位相変化量が $\pi h = \pi/2$ になることを反映しています。位相が「ジグザグ」に変化する様子は MSK のトレリス図と直接対応しており、ビタビ復号との親和性が高いことを示唆しています。

ASK/FSK の BER シミュレーション

コヒーレント検波の BER 曲線

理論で導いた BER 特性を Monte Carlo シミュレーションで検証します。OOK、コヒーレント BFSK、MSK の BER を $E_b/N_0$ の関数として描きます。

import numpy as np
import matplotlib.pyplot as plt
from scipy.special import erfc

def simulate_ook_ber(snr_db_range, n_bits=100000):
    """OOKのコヒーレント検波BERシミュレーション"""
    ber_list = []
    for snr_db in snr_db_range:
        snr_lin = 10 ** (snr_db / 10)
        # OOK: Eb = A^2*Ts/4 (平均), sigma^2 = N0/2
        A = 2.0
        Eb = A ** 2 / 4  # 平均ビットエネルギー(Ts=1と正規化)
        N0 = Eb / snr_lin
        sigma = np.sqrt(N0 / 2)

        bits = np.random.randint(0, 2, n_bits)
        # 整合フィルタ出力
        z = bits * A + sigma * np.random.randn(n_bits)
        # 最適閾値はA/2
        bits_hat = (z > A / 2).astype(int)
        ber = np.mean(bits != bits_hat)
        ber_list.append(max(ber, 1e-7))
    return np.array(ber_list)

def simulate_bfsk_coherent_ber(snr_db_range, n_bits=100000):
    """コヒーレントBFSKのBERシミュレーション"""
    ber_list = []
    for snr_db in snr_db_range:
        snr_lin = 10 ** (snr_db / 10)
        Eb = 1.0
        N0 = Eb / snr_lin
        sigma = np.sqrt(N0 / 2)

        bits = np.random.randint(0, 2, n_bits)
        # 2つの相関器出力(直交信号)
        # ビット=0: s0成分=sqrt(Eb), s1成分=0
        # ビット=1: s0成分=0, s1成分=sqrt(Eb)
        z0 = (1 - bits) * np.sqrt(Eb) + sigma * np.random.randn(n_bits)
        z1 = bits * np.sqrt(Eb) + sigma * np.random.randn(n_bits)
        # 判定: z1 > z0 なら 1
        bits_hat = (z1 > z0).astype(int)
        ber = np.mean(bits != bits_hat)
        ber_list.append(max(ber, 1e-7))
    return np.array(ber_list)

def simulate_bfsk_noncoherent_ber(snr_db_range, n_bits=100000):
    """非コヒーレントBFSKのBERシミュレーション"""
    ber_list = []
    for snr_db in snr_db_range:
        snr_lin = 10 ** (snr_db / 10)
        Eb = 1.0
        N0 = Eb / snr_lin
        sigma = np.sqrt(N0 / 2)

        bits = np.random.randint(0, 2, n_bits)
        # 包絡線検波: 各フィルタの I/Q 出力
        z0_I = (1 - bits) * np.sqrt(Eb) + sigma * np.random.randn(n_bits)
        z0_Q = sigma * np.random.randn(n_bits)
        z1_I = bits * np.sqrt(Eb) + sigma * np.random.randn(n_bits)
        z1_Q = sigma * np.random.randn(n_bits)
        # 包絡線
        env0 = np.sqrt(z0_I**2 + z0_Q**2)
        env1 = np.sqrt(z1_I**2 + z1_Q**2)
        bits_hat = (env1 > env0).astype(int)
        ber = np.mean(bits != bits_hat)
        ber_list.append(max(ber, 1e-7))
    return np.array(ber_list)

# シミュレーション実行
snr_db = np.arange(0, 16, 1)

ber_ook = simulate_ook_ber(snr_db, n_bits=200000)
ber_bfsk_coh = simulate_bfsk_coherent_ber(snr_db, n_bits=200000)
ber_bfsk_ncoh = simulate_bfsk_noncoherent_ber(snr_db, n_bits=200000)

# 理論曲線
snr_fine = np.linspace(0, 15, 200)
snr_lin_fine = 10 ** (snr_fine / 10)

# BPSK理論値
ber_bpsk_theory = 0.5 * erfc(np.sqrt(snr_lin_fine))
# OOK理論値
ber_ook_theory = 0.5 * erfc(np.sqrt(snr_lin_fine / 2))
# コヒーレントBFSK理論値
ber_bfsk_coh_theory = 0.5 * erfc(np.sqrt(snr_lin_fine / 2))
# 非コヒーレントBFSK理論値
ber_bfsk_ncoh_theory = 0.5 * np.exp(-snr_lin_fine / 2)

fig, ax = plt.subplots(figsize=(10, 7))

# シミュレーション結果
ax.semilogy(snr_db, ber_ook, 'rs', markersize=6, label='OOK (sim)')
ax.semilogy(snr_db, ber_bfsk_coh, 'b^', markersize=6, label='Coherent BFSK (sim)')
ax.semilogy(snr_db, ber_bfsk_ncoh, 'gD', markersize=5, label='Non-coherent BFSK (sim)')

# 理論曲線
ax.semilogy(snr_fine, ber_bpsk_theory, 'k-', linewidth=2, label='BPSK/MSK (theory)')
ax.semilogy(snr_fine, ber_ook_theory, 'r--', linewidth=1.5, label='OOK (theory)')
ax.semilogy(snr_fine, ber_bfsk_coh_theory, 'b--', linewidth=1.5, label='Coherent BFSK (theory)')
ax.semilogy(snr_fine, ber_bfsk_ncoh_theory, 'g--', linewidth=1.5, label='Non-coherent BFSK (theory)')

ax.set_xlabel('Eb/N0 [dB]')
ax.set_ylabel('BER')
ax.set_title('BER Comparison: ASK and FSK Modulation Schemes')
ax.legend(loc='lower left')
ax.grid(True, which='both', alpha=0.3)
ax.set_ylim(1e-6, 1)
ax.set_xlim(0, 15)

plt.tight_layout()
plt.savefig('ask_fsk_ber.png', dpi=150, bbox_inches='tight')
plt.show()

このBER曲線から、理論解析で導いた結果が数値的に確認できます。

  1. OOK とコヒーレント BFSK は同じ BER 性能: シミュレーション結果(赤い四角と青い三角)はそれぞれの理論曲線にほぼ一致しています。両方式とも BPSK/MSK に対して約 3 dB の劣化があります。同じ BER $= 10^{-5}$ を達成するために、BPSK/MSK は約 9.5 dB の $E_b/N_0$ で済むのに対し、OOK と コヒーレント BFSK は約 12.5 dB が必要です。
  2. 非コヒーレント BFSK はさらに約 1 dB 劣化: 緑のダイヤモンドで示した非コヒーレント BFSK は、コヒーレント BFSK からさらに約 0.5-1 dB 劣化しています。ただし、位相同期回路が不要な分、受信機の構成が大幅に簡略化できるため、低コスト・低消費電力のアプリケーションでは十分な選択肢です。
  3. BPSK/MSK が最も優れた BER 性能: 黒の実線で示した BPSK/MSK が最も低い BER を達成しています。MSK は BPSK と同じ BER 性能でありながら帯域幅が狭い($1.5R_b$ vs $2R_b$)ため、総合的に最も優れた2値変調方式と言えます。

M-FSK の BER 性能比較

$M$ を増やした場合の FSK の BER 性能を確認します。M-FSK では、$M$ を増やすと BER が改善される(M-ASK とは逆の傾向)ことを理論で示しました。

import numpy as np
import matplotlib.pyplot as plt
from scipy.special import erfc

def simulate_mfsk_ber(M, snr_db_range, n_symbols=100000):
    """M-FSKのコヒーレント検波BERシミュレーション"""
    m = int(np.log2(M))
    ber_list = []

    for snr_db in snr_db_range:
        snr_lin = 10 ** (snr_db / 10)
        Eb = 1.0
        Es = Eb * m  # シンボルエネルギー
        N0 = Eb / snr_lin
        sigma = np.sqrt(N0 / 2)

        # ランダムシンボル
        symbol_indices = np.random.randint(0, M, n_symbols)
        bit_errors = 0
        total_bits = 0

        # M個の相関器出力をベクトル化
        # 送信シンボルiの場合: z_i ~ N(sqrt(Es), sigma^2), z_j ~ N(0, sigma^2) (j != i)
        z = sigma * np.random.randn(n_symbols, M)
        for n in range(n_symbols):
            z[n, symbol_indices[n]] += np.sqrt(Es)

        # 最大値の相関器を選択
        detected = np.argmax(z, axis=1)

        # ビット誤り数をカウント(Gray符号は一般にM-FSKには適用しにくいので、
        # シンボル誤りあたりの平均ビット誤り数 = m*M/(2*(M-1)) を使う)
        symbol_errors = np.sum(detected != symbol_indices)
        # シンボル誤りをビット誤りに変換(M直交信号の場合)
        ber = symbol_errors / n_symbols * (M / 2) / (M - 1)
        ber_list.append(max(ber, 1e-7))

    return np.array(ber_list)

snr_db = np.arange(0, 18, 1)

fig, ax = plt.subplots(figsize=(10, 7))

for M in [2, 4, 8, 16]:
    ber = simulate_mfsk_ber(M, snr_db, n_symbols=200000)
    ax.semilogy(snr_db, ber, 'o-', markersize=5, label=f'{M}-FSK (sim)')

# BPSK理論値
snr_fine = np.linspace(0, 17, 200)
snr_lin_fine = 10 ** (snr_fine / 10)
ber_bpsk = 0.5 * erfc(np.sqrt(snr_lin_fine))
ax.semilogy(snr_fine, ber_bpsk, 'k--', linewidth=2, label='BPSK (theory)')

# コヒーレントBFSK理論値
ber_bfsk_theory = 0.5 * erfc(np.sqrt(snr_lin_fine / 2))
ax.semilogy(snr_fine, ber_bfsk_theory, 'b--', linewidth=1.5, label='BFSK (theory)')

ax.set_xlabel('Eb/N0 [dB]')
ax.set_ylabel('BER')
ax.set_title('BER of M-FSK (Coherent Detection)')
ax.legend()
ax.grid(True, which='both', alpha=0.3)
ax.set_ylim(1e-6, 1)
ax.set_xlim(0, 17)

plt.tight_layout()
plt.savefig('mfsk_ber.png', dpi=150, bbox_inches='tight')
plt.show()

このグラフから M-FSK の重要な性質が確認できます。

  1. $M$ を増やすと BER が改善: 2-FSK から 4-FSK、8-FSK、16-FSK と $M$ を増やすにつれて、同じ $E_b/N_0$ に対する BER が低下しています。これは M-ASK とは逆の傾向であり、M-FSK の大きな特長です。
  2. 改善幅は次第に減少: 2-FSK から 4-FSK への改善は大きいですが、8-FSK から 16-FSK への改善は比較的小さくなっています。理論的には $M \to \infty$ でシャノン限界 $E_b/N_0 = \ln 2 \approx -1.59$ dB に漸近しますが、収束は緩やかです。
  3. 帯域幅とのトレードオフ: 性能改善の代償として帯域幅が $M$ に比例して増大することを忘れてはなりません。16-FSK は 2-FSK に比べて約8倍の帯域幅を必要とします。

ASK と FSK のスペクトル比較

最後に、同じビットレートでの各変調方式のパワースペクトル密度を比較し、帯域幅効率の違いを視覚的に確認します。

import numpy as np
import matplotlib.pyplot as plt

# パラメータ
fc = 50.0       # 搬送波周波数
Rb = 10.0       # ビットレート
Ts = 1.0 / Rb   # ビット周期
fs = 10000.0    # サンプリング周波数
N_bits = 1000   # ビット数

np.random.seed(42)
bits = np.random.randint(0, 2, N_bits)
symbols_bipolar = 2 * bits - 1

t_total = N_bits * Ts
t = np.arange(0, t_total, 1/fs)

# OOK信号
ook = np.zeros(len(t))
for i in range(N_bits):
    mask = (t >= i * Ts) & (t < (i + 1) * Ts)
    ook[mask] = bits[i] * np.cos(2 * np.pi * fc * t[mask])

# BPSK信号(参考)
bpsk = np.zeros(len(t))
for i in range(N_bits):
    mask = (t >= i * Ts) & (t < (i + 1) * Ts)
    bpsk[mask] = symbols_bipolar[i] * np.cos(2 * np.pi * fc * t[mask])

# BFSK信号(h=1)
delta_f_bfsk = Rb  # h = delta_f * Ts = 1
f0_bfsk = fc - delta_f_bfsk / 2
f1_bfsk = fc + delta_f_bfsk / 2
bfsk = np.zeros(len(t))
for i in range(N_bits):
    mask = (t >= i * Ts) & (t < (i + 1) * Ts)
    freq = f1_bfsk if bits[i] == 1 else f0_bfsk
    bfsk[mask] = np.cos(2 * np.pi * freq * t[mask])

# MSK信号(h=0.5, CPFSK)
delta_f_msk = 0.5 * Rb
msk = np.zeros(len(t))
phase_msk = 0.0
dt = 1.0 / fs
for i in range(len(t)):
    sym_idx = min(int(t[i] / Ts), N_bits - 1)
    freq_dev = symbols_bipolar[sym_idx] * delta_f_msk / 2
    msk[i] = np.cos(2 * np.pi * fc * t[i] + phase_msk)
    phase_msk += 2 * np.pi * freq_dev * dt

# スペクトル計算
from numpy.fft import fft, fftfreq
N_fft = len(t)
freq_axis = fftfreq(N_fft, 1/fs)

signals = {'OOK': ook, 'BPSK': bpsk, 'BFSK (h=1)': bfsk, 'MSK (h=0.5)': msk}
colors = {'OOK': 'tab:red', 'BPSK': 'black', 'BFSK (h=1)': 'tab:blue', 'MSK (h=0.5)': 'tab:green'}

fig, ax = plt.subplots(figsize=(12, 7))
mask_freq = (freq_axis >= 30) & (freq_axis <= 70)

for name, sig in signals.items():
    spec = np.abs(fft(sig)) ** 2 / N_fft
    # 移動平均でスムージング
    window = 50
    spec_smooth = np.convolve(spec, np.ones(window)/window, mode='same')
    psd_db = 10 * np.log10(spec_smooth[mask_freq] / np.max(spec_smooth[mask_freq]) + 1e-15)
    ax.plot(freq_axis[mask_freq], psd_db, linewidth=1.5, color=colors[name],
            label=name, alpha=0.8)

ax.set_xlabel('Frequency [Hz]')
ax.set_ylabel('Normalized PSD [dB]')
ax.set_title(f'Spectral Comparison of Modulation Schemes (fc={fc} Hz, Rb={Rb} bit/s)')
ax.legend()
ax.grid(True, alpha=0.3)
ax.set_ylim(-40, 5)
ax.axvline(x=fc, color='gray', linestyle=':', alpha=0.5)

plt.tight_layout()
plt.savefig('spectrum_comparison.png', dpi=150, bbox_inches='tight')
plt.show()

このスペクトル比較図から、各変調方式の帯域幅特性が視覚的に把握できます。

  1. OOK と BPSK は同じ帯域幅: 両方式とも搬送波周波数 $f_c$ を中心に $\pm R_b$ の帯域(ヌル間 $2R_b = 20$ Hz)を占有しています。OOK はスペクトルの形状がわずかに異なりますが(直流成分があるため)、帯域幅は同等です。
  2. BFSK は最も帯域幅が広い: $h = 1$ の BFSK は $f_0$ と $f_1$ のそれぞれにメインローブが現れ、全体として $3R_b = 30$ Hz の帯域幅を占有しています。帯域効率の観点では最も不利です。
  3. MSK は最もコンパクトなスペクトル: $h = 0.5$ の MSK は半正弦波パルス整形の効果により、メインローブが狭く、サイドローブの減衰が速いことがわかります。帯域幅は $1.5R_b = 15$ Hz で、BPSK よりも25%狭い帯域で同じ情報量を伝送しています。

ASK と FSK の応用と使い分け

応用先の整理

ここまでの理論とシミュレーションを踏まえ、ASK と FSK の使い分けを整理します。

ASK(特にOOK)が適する場面: – 受信機の構成を極限まで簡略化したい場合(包絡線検波のみで復調可能) – 消費電力が厳しく制限される場合(送信しない時間帯があるため省電力) – 帯域幅が十分にある場合 – 具体例: RFID、赤外線通信、光ファイバー通信(IM/DD方式)

FSK が適する場面: – 振幅変動が大きい伝搬路(フェージング環境) – 非線形チャネル(衛星中継器のTWTA等)— 包絡線一定のCPFSK/MSKが有利 – 中程度のデータレートで信頼性を重視する場合 – 具体例: Bluetooth (GFSK)、LoRa、航空・海事通信、アマチュア無線

MSK/GMSK が適する場面: – 帯域効率と電力効率の両方が重要な場合 – 非線形増幅器を使いたい場合(包絡線一定) – 具体例: GSM(GMSK)、航空データリンク

ASK/FSK vs PSK/QAM

ASK と FSK を PSK(位相偏移変調)や QAM(直交振幅変調)と比較すると、以下の位置づけが見えてきます。

  • 帯域効率: QAM > PSK > M-ASK > MSK > OOK > FSK
  • 電力効率(BER性能): M-FSK(大M)> BPSK = MSK > QPSK > OOK = BFSK > M-ASK(大M)
  • 実装の簡易さ: OOK > BFSK > MSK > QPSK > QAM
  • フェージング耐性: FSK > PSK > ASK

通信システムの設計では、これらのトレードオフを勘案して最適な変調方式を選択します。高速大容量通信(4G/5G)では QAM が主流ですが、IoT や低消費電力通信では OOK や FSK の素朴な方式が今なお現役です。

まとめ

本記事では、ASK(振幅偏移変調)と FSK(周波数偏移変調)の原理から数学的記述、BER特性、Python実装までを体系的に解説しました。

  • ASK の原理: 搬送波の振幅をデジタルデータに応じて切り替える変調方式。OOK($A_0 = 0, A_1 = A$)が最も基本的。M-ASK は1次元のコンスタレーションで帯域効率に優れるが、$M$ 増加とともに雑音耐性が劣化する
  • FSK の原理: 搬送波の周波数を切り替える変調方式。変調指数 $h = \Delta f \cdot T_s$ が性質を決定する。CPFSK は位相連続性によりスペクトルがコンパクトになる
  • MSK: $h = 0.5$ の CPFSK。BPSK と同じ BER 性能を持ちながら帯域幅が25%狭い。包絡線一定で非線形増幅器に適合する。OQPSK + 半正弦波パルス整形と等価
  • 直交条件: FSK 信号の直交条件は $h = k/2$($k$は正の整数)。最小直交変調指数 $h = 0.5$ が MSK
  • BER 特性: BPSK/MSK が最も優れ、OOK とコヒーレント BFSK は約3dB劣る。M-FSK は $M$ 増加で BER 改善(帯域幅は増大)、M-ASK は $M$ 増加で BER 劣化(帯域幅は縮小)
  • 帯域幅のトレードオフ: ASK は $M$ 増加で帯域効率向上、FSK は $M$ 増加で帯域幅増大。両者は帯域効率と電力効率のトレードオフの両端に位置する

ASK と FSK は、デジタル変調の中で最もシンプルな方式ですが、その理論的枠組みはPSKやQAMの理解にも直結します。特に、信号空間(コンスタレーション)の概念、直交性の条件、BER と $E_b/N_0$ の関係は、全ての変調方式に共通する基盤です。

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