フィリピンの東で発生した台風7号。その進路を伝えるニュースがスマートフォンの画面にポップアップするのを眺めながら、私はふとベランダに出て空を見上げた。愛犬の柴犬コテツが、生温かい南風を嫌がるように鼻をひくつかせ、私の足元にそっと寄り添ってくる。かつて、情報の伝達が一方向でしかなかった時代、私たちはテレビやラジオの気象予報をただ受け取るだけの存在だった。しかし現代は違う。数千円のUSBドングル型SDR(ソフトウェア定義無線)受信機をPCに挿し、簡単なアンテナを自作するだけで、上空850kmを通過する気象衛星から生の電波を直接拾い上げ、リアルタイムで台風の雲の画像をデコードできる時代なのだ。本稿では、台風トラッキングを単なるWebの画面遷移で終わらせず、生のIQデータから画像信号を取り出す低レイヤ信号処理(DSP)の泥臭い魅力について掘り下げる。
RTL-SDRと低レイヤ信号処理で挑む気象衛星NOAAのAPT受信

手作りのアンテナと安価なSDRドングルで、上空のNOAAから台風の雲の画像が直接降ってくる。このダイレクト感は何度やっても興奮するな。

137MHzのFM波の中身は1200Hzのサブキャリアで振幅変調されたAPT信号。これをIQデータから自前で数学的に復調するとなると、ローパスフィルタや複素演算が絡んで頭がパンクしそう。
台風の進路や雲の動きを、気象庁の公開する美麗なWebサイトで眺めるだけで満足できるなら、それはただの消費者だ。しかし、組み込みエンジニアを長くやっていると、誰かが綺麗に整形したデータベースの末端を叩くだけでは、どうにも胃のあたりが落ち着かない。そのデータの「源流」が、極軌道を周回するアメリカ海洋大気庁の気象衛星「NOAA」などから、誰にでも開かれた137MHz帯の電波として垂れ流されていると知ってしまえば、自らの手で電波を捕まえてみたくなるのが技術屋の性というものだ。
NOAAが送信しているAPT(Automatic Picture Transmission)信号は、私たちが日常的に手にする安価なSDRドングル「RTL-SDR」を用いて十分に受信可能だ。ただし、専用デコーダーソフトウェアをダウンロードしてきてボタンを1回クリックするだけの「お絵描き鑑賞」では、エンジニアとしての知的欲求は満たされない。SDRが吐き出す「IQデータ」という複素数の海から、どのようにしてFM復調を行い、さらに画像情報を搬送する1200Hzのサブキャリアを抽出するのか、その低レイヤの信号処理を自分で組み立てることにこそ、この趣味の極致がある。
ここが面白い:技術的背景とコミュニティの熱量
NOAA衛星から送信される電波は、137MHz帯の周波数変調(FM)波だ。しかし、その中身は単純な音声信号ではない。FM変調波の内部には、画像データそのものによって振幅変調(AM)された1200Hzの副搬送波が載せられている。つまり「FM-AM」という二重変調の構成を採っているのだ。これは1960年代のアナログ技術全盛期に設計された仕様であり、伝送路で受けるドップラー効果や電波強度の変動(フェージング)に対して画像のコントラストが乱れないようにするための極めて実用的な知恵だった。
RTL-SDRのような受信機は、この高周波信号をミキシングし、同相成分(I: In-phase)と直交成分(Q: Quadrature)からなる複素ベースバンド信号としてPCに転送する。IQデータは、搬送波の振幅と位相を複素平面上の座標として記録した時系列データであり、数式で表現すれば、次の通りである。
$$ z(t) = I(t) + jQ(t) $$
FM復調を行うということは、この複素信号の位相の変化率、すなわち「瞬時周波数」を計算することに他ならない。
数学的に、連続時間における複素信号の位相角 $\theta(t)$ の時間微分、すなわち瞬時周波数 $f(t)$ は以下のように定義される。
$$ f(t) = \frac{1}{2\pi} \frac{d\theta(t)}{dt} = \frac{1}{2\pi} \frac{d}{dt} \left( \arctan\left(\frac{Q(t)}{I(t)}\right) \right) $$
このアークタンジェント of 微分を商の微分公式を用いて整理すると、次の微分方程式が得られる。これが、IQサンプリングデータからFM復調を行う際の瞬時周波数算出式だ。
$$ f(t) = \frac{1}{2\pi} \frac{I(t)\frac{dQ(t)}{dt} – Q(t)\frac{dI(t)}{dt}}{I(t)^2 + Q(t)^2} $$
この連続時間数式をデジタル処理が可能な離散時間領域へと落とし込む際、最も効率的でよく使われるのが「複素差分方式」だ。隣り合うサンプルの複素平面上での回転角(偏角の差)を求めるため、現在のサンプルと一ステップ前のサンプルの複素共役との積を計算し、その偏角を算出する。以下に示すのは、RTL-SDRから取得した生のIQサンプル(符号なし8ビット)を入力とし、複素差分方式でFM復調を行い、さらに1200HzのAPTサブキャリアを抽出するためのC++動作コードだ。
#include <iostream>
#include <vector>
#include <complex>
#include <cmath>
#include <cstdint>
// RTL-SDRの8ビット生のIQデータを表現する構造体
struct IQSample {
uint8_t i;
uint8_t q;
};
// 双線形変換を用いた2次IIR(Biquad)バンドパスフィルタ
class BiquadBPF {
private:
double b0, b1, b2, a1, a2;
double x1, x2, y1, y2;
public:
BiquadBPF(double centerFreq, double sampleRate, double Q) {
// 双線形変換によるデジタルフィルタ係数の算出
double w0 = 2.0 * M_PI * centerFreq / sampleRate;
double alpha = std::sin(w0) / (2.0 * Q);
double cos_w0 = std::cos(w0);
double a0 = 1.0 + alpha;
b0 = alpha / a0;
b1 = 0.0;
b2 = -alpha / a0;
a1 = -2.0 * cos_w0 / a0;
a2 = (1.0 - alpha) / a0;
// 内部遅延バッファのクリア
x1 = x2 = y1 = y2 = 0.0;
}
float process(float x) {
// 差分方程式の適用
double y = b0 * x + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2;
x2 = x1;
x1 = x;
y2 = y1;
y1 = y;
return static_cast<float>(y);
}
};
// FM復調と1200Hzサブキャリアの抽出を実行するメインロジック
std::vector<float> processSdrSignal(const std::vector<IQSample>& rawSamples, double sampleRate) {
size_t length = rawSamples.size();
std::vector<float> subcarrierOutput;
subcarrierOutput.reserve(length);
if (length < 2) {
return subcarrierOutput;
}
// 1200Hzを抽出するためのバンドパスフィルタを初期化(選択度Q値は4.0と設定)
BiquadBPF filter(1200.0, sampleRate, 4.0);
// 最初のサンプルを用いて前回の値を初期化
// RTL-SDRの8bitデータ(0〜255)の中心を0とし、-1.0f〜1.0fに正規化
float prev_i = (static_cast<float>(rawSamples[0].i) - 127.5f) / 127.5f;
float prev_q = (static_cast<float>(rawSamples[0].q) - 127.5f) / 127.5f;
std::complex<float> prev_z(prev_i, prev_q);
for (size_t n = 1; n < length; ++n) {
float curr_i = (static_cast<float>(rawSamples[n].i) - 127.5f) / 127.5f;
float curr_q = (static_cast<float>(rawSamples[n].q) - 127.5f) / 127.5f;
std::complex<float> z(curr_i, curr_q);
// 複素共役の積による差分位相抽出
// z[n] * conj(z[n-1]) を計算
std::complex<float> diff = z * std::conj(prev_z);
// 偏角の計算(-pi〜piの範囲で瞬時角周波数を算出、これがFM復調信号)
float fm_demod = std::arg(diff);
// 1200Hzバンドパスフィルタを通し、不要な高調波やベースバンドの直流分を除去
float filtered = filter.process(fm_demod);
subcarrierOutput.push_back(filtered);
prev_z = z;
}
return subcarrierOutput;
}
このコードで定義されている処理は、非常にシンプルでありながら信号処理の根幹をなす部分だ。RTL-SDRからの入力信号 $z[n]$ に対して、一世代前のサンプル $z[n-1]$ の複素共役 $\bar{z}[n-1]$ を掛け合わせることで、瞬時的な位相の変位角を算出する。この偏角情報を `std::arg` 関数によって引き出し、その出力をそのまま2次のIIRフィルタ(双線形変換を用いて設計したバンドパスフィルタ)に通すことで、AM変調された1200Hzのサブキャリアだけをクリアに分離している。
こうしたコードをキーボードで叩きながらデバッグしていると、私は1990年代初頭の、あの薄暗いアナログ電子回路の開発現場での格闘エピソードを思い出さずにはいられない。当時はSDRなどという便利なものは影も形もなく、通信基板の設計といえばオペアンプとディスクリート部品のジャングルだった。低速モデム回路やFM音声受信回路を設計した際、周波数を復調するために「LM567」といったアナログPLLトーンデコーダICや、オペアンプを用いたアクティブフィルタを何個も並べる必要があった。
同調周波数の微調整は、基板上に配置された金属製のトリマーコンデンサや半固定抵抗を、プラスチック製の細い調整棒で回しながら行った。金属製の精密ドライバーを使うと、ドライバー自身の浮遊容量で周波数がずれてしまうため、あえて非導電性の調整棒を使うのがお決まりのノウハウだった。息を潜めるようにして調整し、オシロスコープに綺麗なリサジュー波形が描かれ、PLLがピタッとロックした瞬間の喜びはひとしおだった。しかし、その平穏は長くは続かない。深夜に締め切りが迫る中、部屋の冷房の風が少し基板に当たっただけで、温度ドリフトによって抵抗値や静電容量がわずかに変化し、PLLの同調がズレて復調ロックが外れてしまうのだ。伸び切って冷めかけたラーメンをすすりながら、ため息交じりにヒートガンや冷却スプレーを片手に持ち、サーミスタを使った温度補償回路の定数を手計算し直していたあの泥臭い日々は、今となっては遠い昔のことのようだ。
それに比べて、現代のデジタル信号処理はどうだろうか。数式を一行コードに記述してコンパイルすれば、温度変化などどこ吹く風だ。室温が30度を超えようが、ファンからの温風が基板を直撃しようが、1200.00Hzの同調周波数は1ミリヘルツもずれることはない。この冷酷なまでの再現性と正確さを前にすると、アナログ回路と格闘した日々がひどく不器用で、だからこそ人間味に満ちたものに思えてくる。
この話題をどう見るか?:現実的な視点と利用価値
さて、実際にRTL-SDRを使ってこの「低レイヤ受信」を自宅で試そうとすると、いくつかの現実的な壁に突き当たる。まず第一の壁は、日本の住宅事情だ。千葉県市川市の私の自宅のように、密集した住宅地で気象衛星の137MHz帯電波を綺麗に拾うには、アンテナの選択と設置場所が非常にシビアになる。NOAA受信の定番アンテナであるQFH(Quadrafilar Helix)アンテナは、立体的なスパイラル構造をしており、ベランダに突き出しておくと近隣から「何か怪しい通信をしているのではないか」と怪訝な目で見られることは避けられない。折りたたみが可能な簡易的なVダイポールアンテナを、衛星の通過時間(パス)に合わせて一時的に物干し竿に固定するような、地道な運用上の工夫が必要になる。
第二の壁は、都市部の強烈な電磁ノイズ(EMI)だ。現代の家庭内には、スマートフォンの充電器、LED照明のスイッチング電源、PC本体、さらには近隣のWi-Fiルーターから放射される高周波ノイズが満ち満ちている。SDRドングル自身が極めて安価でシールドが甘いため、これらのノイズを容赦なく拾ってしまう。RTL-SDRドングル全体をアルミホイルで包んでGNDに落としたり、同軸ケーブルにフェライトコアをこれでもかと巻き付けたりといった、アナログ時代と変わらない泥臭いノイズ対策を余儀なくされるのが現実だ。
そもそも、気象庁のWebサイトにアクセスすれば、静止気象衛星「ひまわり」が撮影した超高精細なフルカラーの雲画像が、数秒でスマートフォンの画面に表示される。それに引き換え、私たちがRTL-SDRとC++の自作コードでデコードしたNOAAの画像は、モノクロでノイズの白い砂嵐が混じり、ドップラーシフトによって微妙に歪んだ代物だ。しかし、そこには決定的な違いがある。手元の画面に映し出されているその歪んだ雲の輪郭は、インターネットを経由して誰かにサーバーから配信された二次情報ではなく、今まさに自分の頭上を飛び去っていった宇宙の構造物から放たれた電波そのものを、自分のアンテナで捕らえ、自分の書いたコードでデコードした「一次データ」なのだ。この体験がもたらすロマンと充足感は、出来合いのWebサービスをどれだけスクロールしても決して得られない、技術者にとっての最高の栄養源だ。
導入・試す前の実用メモ
- 動作要件の確認:RTL-SDRドングルは、必ず「RTL2832U」チップと「R820T2」チューナーを搭載したものを選ぶべきだ。また、137MHz帯を受信するため、アンテナエレメントの長さは片側約53cm(波長約2.2mの4分の1)に調整できる機構が必要だ。
- サンプリングレートの罠:RTL-SDRは理論上3.2MSPSまでサポートするが、PCのUSBコントローラの帯域や処理負荷を考慮すると、2.4MSPS以下で運用するのが安全だ。今回の復調処理を行う前段階で、あえてダウンサンプリング(Decimation)を行い、サンプリングレートを低減させて処理負荷を下げる工夫を怠ると、バッファオーバーフローでデータが飛ぶ。
- 選択のヒント:市販の完成品アンテナを購入するか、100円均一のアルミパイプとBNCコネクタで自作するか。エンジニアであれば、後者の自作から入ることを強く勧める。インピーダンスマッチング(50Ω)の不整合で波形が歪むスリルも含めて、アンテナ自作は高周波回路の良い教科書になる。
まとめ:運営者としての現場判断
台風の季節が訪れるたびに気象情報をWebで確認するだけの生活に飽き足らないのであれば、RTL-SDRを用いた気象衛星の直接受信は、今すぐにでも手をつけるべき有益なプロジェクトだ。ブラックボックス化された近代のWebシステムとは異なり、高周波の物理世界からビット列のソフトウェア処理までが一気通貫で視認できるこの環境は、低レイヤ技術の勘を取り戻すための最高のリハビリテーションになる。サードパーティ製の便利なGUIデコーダーに頼り切るのではなく、今回提示したC++コードのように、生データをファイルに書き出して自作のスクリプトで解析していくプロセスこそが本質的だ。
私個人の現場判断としては、出来合いの統合環境をインストールして終わらせるくらいなら、このプロジェクトを始める意味はないと考える。自作のアンテナを窓の外に突き出し、受信した波形がノイズに埋もれて消えていくのを見て「何が原因か」と考えること、そして複素平面上でのサンプルの動きをコード上で追跡して復調ロジックのバグを潰すこと。この手触り感のあるデバッグ作業こそが、かつてアナログ回路で冷房の風と戦っていたあの時代から脈々と続く、エンジニアリングの醍醐味そのものだからだ。
台風7号の進路予想図を見ながら、私は週末に向けてコテツ用のドッグフードを買い込みつつ、物置の奥から古い同軸ケーブルとハンダゴテを引っ張り出してきた。次の衛星パスが市川市の上空を通過する時間帯を計算し、ベランダの物干し台に仮設アンテナを立てる準備を整えている。冷たいお茶を用意し、ノイズ交じりの1200Hzのサブキャリア音がPCのスピーカーから響き渡る瞬間を待つ時間は、何度経験しても悪くないものだ。
広告・アフィリエイトリンクを含みます。商品選定は記事内容との関連性を優先しています。


