Week 11 — 通訊系統

資料夾:week11_comm/

基礎知識導論

Week 11 基礎知識導論 — 通訊鏈路預算

鏈路預算是一串乘法(在 dB 域是一串加減)。
任何一個參數靜默沒生效,報告照樣會印出一個看起來很合理的餘裕值 —— 而且是錯的。
這一週是全課「ACK 不等於生效」的示範週。

1. 這一週在解什麼問題

FIREYE 在 550 km、地面站在賓州(AGI HQ)。
UHF 下行、VHF 上行,要求餘裕 >= 3 dB。

問題是:發射功率要多大?天線增益要多少?可以跑多快的位元率?
下雨會不會斷?都卜勒要補多少?

2. 核心觀念

2.1 鏈路方程

Eb/N0 [dB] = EIRP - L_FS - L_atm - L_pol - L_point + G/T - k - 10*log10(Rb)

逐項拆解:

項 意義 典型量級(LEO UHF)
EIRP 發射功率 + 天線增益 − 饋線損耗 (dBW) 衛星端 0–5 dBW
L_FS 自由空間損耗 約 150 dB
L_atm 大氣吸收 UHF < 0.5 dB;Ka 頻段大很多
L_pol 極化失配 圓對線性固定 3 dB
L_point 指向損耗 0.5–3 dB
G/T 接收品質因數 (dB/K) 地面站 −10 至 +10
k Boltzmann 常數 −228.6 dBW/(K·Hz)
Rb 位元率 9.6 kbps 至 数 Mbps

2.2 自由空間損耗

L_FS = 20*log10( 4*pi*d / λ )   dB          λ = c / f
     = 92.45 + 20*log10(d_km) + 20*log10(f_GHz)   dB

兩個推論:

  1. 距離加倍,損耗增加 6 dB。 過頂(550 km)與低仰角(約 2000 km 斜距)差約 11 dB ——
    所以低仰角時鏈路最緊,鏈路預算要用最壞斜距算,不是用最短距離。
  2. 頻率加倍,損耗也增加 6 dB。 但高頻可以用更小的天線得到更高增益
    (G ∝ (D/λ)²),兩者部分抵消。真正的取捨在天線尺寸、指向精度與大氣衰減。

2.3 天線增益與波束寬

拋物面天線:

G = η_a * ( pi * D / λ )²                    η_a 約 0.5–0.7
θ_3dB ≈ 70 * λ / D    [度]

增益與波束寬是同一件事的兩面:增益高 = 波束窄 = 指向要求嚴。
一個 30 dBi 的天線波束寬只有幾度,若 ADCS 指不準,增益等於白給。

這是通訊與姿態(Week 8)最直接的耦合。
CubeSat 常用低增益的偶極/單極/turnstile 天線,就是為了容忍姿態誤差。

2.4 G/T:接收端的品質因數

G/T = G_rx - 10*log10( T_sys )     [dB/K]
T_sys = T_ant + T_LNA + T_line + ...

T_sys 是系統雜訊溫度。地面站的低雜訊放大器 (LNA) 直接決定它。
G/T 把「增益」與「雜訊」合成一個數 —— 這是比較接收系統的標準指標。

本課實測踩過的坑:STK 的 Model.GOverT 欄位吃的是線性比值,不是 dB。
寫 Model.GOverT -10 想表示 −10 dB,會被拒絕。正確寫法是 0.1。
dB 與線性混用是鏈路預算最常見的錯誤來源。

2.5 Eb/N0 門檻與編碼增益

調變 / 編碼 BER 1e-5 所需 Eb/N0
未編碼 BPSK 約 9.6 dB
卷積碼 (k=7, r=1/2) 約 4.5 dB
卷積 + Reed-Solomon 串接 約 2.5 dB
Turbo / LDPC 接近 Shannon 極限(約 1 dB)

編碼增益是最便宜的鏈路餘裕:加 5 dB 編碼增益,等於發射功率變 3 倍,
或天線口徑變 1.8 倍 —— 但編碼只花數位訊號處理的功耗。

代價是頻寬變寬(r=1/2 表示符元率變兩倍)與延遲。

2.6 餘裕:為什麼是 3 dB

Margin = Eb/N0(實際)  -  Eb/N0(門檻)

3 dB 餘裕代表「功率再掉一半也還能通」。它涵蓋的是:

餘裕不是安全係數,是對模型不確定度的定量承認(跟 Week 3 的餘裕哲學一致)。

2.7 都卜勒

LEO 過頂時的徑向速度可達 ±7.5 km/s:

Δf / f = v_r / c        →   UHF 437 MHz 時約 ±10 kHz
                            S 頻段 2.2 GHz 時約 ±55 kHz

而且變化率最大的時刻正是過頂前後(仰角最高、訊號最強的時候)。
所以接收機要能追蹤,或地面站要預先補償(用軌道預報算出頻率曲線)。

2.8 頻譜與法規

CubeSat 的 UHF/VHF 常用業餘無線電頻段。這需要:

業餘頻段有嚴格的條件:非商業用途、必須公開遙測格式、發射功率受限。
這是一個法規驅動的設計限制,不是技術限制 —— 跟 Week 1 講的 CDS 一樣。

3. 名詞與符號

符號 意義 單位 值 / 典型
EIRP 等效全向輻射功率 dBW —
L_FS 自由空間損耗 dB 約 150(UHF, LEO)
G/T 接收品質因數 dB/K STK 欄位吃線性值
k Boltzmann 常數 dBW/(K·Hz) −228.6
Eb/N0 位元能量對雜訊密度比 dB 門檻 9.6(未編碼 BPSK)
Rb 位元率 bit/s —
T_sys 系統雜訊溫度 K —
θ_3dB 半功率波束寬 ° 70λ/D
Margin 餘裕 dB 需求 >= 3

4. 觀念 → 本週實驗對照

觀念 出現在本週的哪裡
雙向鏈路建構 STKSat_Comm_Connect.txt:UHF 下行 + VHF 上行
Link Budget 報告 樣式 "Link Budget - Detailed"
餘裕判定 week11_verify.py 取最差 Eb/No 減門檻 9.6 dB
RF 環境 雨衰與大氣模型設定
回讀驗證 Transmitter_RM / Receiver_RM ... GetValue

本週是驗證段的試點週。實測 37 項全 PASS
(最差 Eb/No 47.12 dB,餘裕 37.52 dB)。
導入這一段時抓出三個 0 個 NACK 的靜默失敗:
1. Model.DesignFrequency 屬性不存在,要寫 Model.AntennaControl.Antenna.DesignFrequency
2. 設定 Model.AntennaControl.Antenna Isotropic 會沖掉先前寫入的設計頻率,
回到 14.5 GHz 預設值 —— 純粹是指令順序問題
3. STKSat 沿用 Lab1 的掃描起點(250 km / i = 0),鏈路完全沒有 access

三個都是 ACK 正常、報告照印、數字全錯。這就是本課要教的東西。

5. 常見誤解

  1. 「dB 可以相乘。」 — dB 是對數,加減對應線性的乘除。G/T = -10 的坑就是這樣來的。
  2. 「用最短斜距算鏈路。」 — 要用最壞情況(最低仰角)。差 11 dB 以上。
  3. 「餘裕 37 dB 太浪費,把功率調小。」 — 先確認那是不是最差時刻的值。
    本週的 47.12 dB 已經是最差 Eb/No 了,但要注意分析窗與 access 是否涵蓋低仰角。
  4. 「增益愈高愈好。」 — 增益高 = 波束窄 = 對 ADCS 的要求上升。整體可能更差。
  5. 「報告有印出數字就是算對了。」 — 三個靜默失敗都會印出漂亮的數字。
    要回讀每個輸入參數。

6. 延伸閱讀

鏈路預算基礎

法規與頻譜

地面站與實作

工具

7. 課前自我檢核

  1. 550 km 過頂與 5° 仰角時的斜距各是多少?自由空間損耗差幾 dB?
  2. G/T 要填 −10 dB,在 STK 的欄位裡應該寫什麼數字?為什麼?
  3. 加一組 r=1/2 卷積碼,餘裕增加多少?付出的代價是什麼?
  4. 天線增益從 3 dBi 換成 20 dBi,對 ADCS 的指向需求有什麼影響?
  5. 鏈路報告印出了漂亮的餘裕值。列出三種方法證明輸入參數真的生效了。

程式碼

week11_comm/STKSat_Comm_Connect.txt
# ============================================================
# STKSat Communications Link - STK Connect Command Script
# Applies the UT<->Satellite comm-scenario conventions from
# the provided markdown reference to SmallSat Part 3
# (Communications Design):
#   Downlink  435 MHz UHF  (STKSat Tx -> Ground Rx)
#   Uplink    145 MHz VHF  (Ground Tx -> STKSat Rx)
#   Required link margin >= 3 dB
#
# PREREQUISITE - enable Connect server before running:
#   Edit > Preferences > Connect  ->  tick "Allow Connect",
#   TCP/IP port 5001, tick Verbose, Apply/OK.
# Batch-load with:  # ConFile / "F:\stk\STKSat_Comm_Connect.txt"
#
# Run AFTER OrbitDesignLab_Connect.txt (reuses */Satellite/STKSat),
# or uncomment the STANDALONE block to create the satellite here.
# ============================================================

# ---- STANDALONE (uncomment if STKSat does not yet exist) ----
#New / Scenario STKSat_Comms
#SetAnalysisTimePeriod * "1 Jan 2024 00:00:00.000" "2 Jan 2024 00:00:00.000"
#SetTimePeriod * "1 Jan 2024 00:00:00.000" "2 Jan 2024 00:00:00.000"
#Animate * Reset
#New / */Satellite STKSat
#OrbitWizard */Satellite/STKSat OrbitDesigner SemimajorAxis 6928 Eccentricity 0 Inclination 56 ArgumentOfPerigee 0 RAAN 0 TrueAnomaly 0

# ---- Session distance unit ----
SetUnits / km

# ============================================================
# Ground station: AGI HQ, Exton PA (per SmallSat mission brief)
# ============================================================
# ---- 採用 Week 4 掃描「選定」的任務軌道 ----
# OrbitDesignLab_Connect.txt 把 STKSat 設成 a=6628 km, i=0 —— 那是 Lab1
# (a, i) 掃描的**起點**,不是結論。赤道 250 km 軌道對北緯 40 度的測站
# 完全沒有 access,鏈路預算會印出 "Data Unavailable",而所有指令照樣 ACK。
# 通訊週要用的是掃描選出來的任務軌道:550 km / i=56 度。
SetState */Satellite/STKSat Classical J2Perturbation "UseAnalysisStartTime" "UseAnalysisStopTime" 60 ICRF "UseAnalysisStartTime" 6928.0 0.0 56.0 0.0 0.0 0.0 TrueAnomaly

New / */Facility AGI_HQ
SetPosition */Facility/AGI_HQ Geodetic 40.0416 -75.5983 0.1
SetConstraint */Facility/AGI_HQ ElevationAngle Min 5.0

# ============================================================
# DOWNLINK  435 MHz UHF : STKSat transmitter -> AGI_HQ receiver
# ============================================================
# --- Satellite transmitter (nadir sensor host) ---
New / */Satellite/STKSat/Sensor CommSensor
Point */Satellite/STKSat/Sensor/CommSensor Targeted Tracking Facility/AGI_HQ Hold
New / */Satellite/STKSat/Sensor/CommSensor/Transmitter Sat_UHF_Tx

Transmitter */Satellite/STKSat/Sensor/CommSensor/Transmitter/Sat_UHF_Tx SetValue Model Complex_Transmitter_Model
# Frequency in Hz (435 MHz), power per mission cap (6 W tx budget)
Transmitter */Satellite/STKSat/Sensor/CommSensor/Transmitter/Sat_UHF_Tx SetValue Model.Frequency 435000000.0
Transmitter */Satellite/STKSat/Sensor/CommSensor/Transmitter/Sat_UHF_Tx SetValue Model.Power 2.0
# CubeSat health/imagery downlink data rate (b/sec)
Transmitter */Satellite/STKSat/Sensor/CommSensor/Transmitter/Sat_UHF_Tx SetValue Model.DataRate 9600.0
# Simple dipole-like embedded antenna
Transmitter */Satellite/STKSat/Sensor/CommSensor/Transmitter/Sat_UHF_Tx SetValue Model.AntennaControl.Antenna Isotropic
# 天線型別要先設:改 Antenna 會換掉天線物件,之前寫的 DesignFrequency
# 會被沖回 STK 預設的 14.5 GHz(ACK 照給,但值沒了)
Transmitter */Satellite/STKSat/Sensor/CommSensor/Transmitter/Sat_UHF_Tx SetValue Model.AntennaControl.Antenna.DesignFrequency 435000000.0
Transmitter */Satellite/STKSat/Sensor/CommSensor/Transmitter/Sat_UHF_Tx SetValue Model.Modulator BPSK
Transmitter */Satellite/STKSat/Sensor/CommSensor/Transmitter/Sat_UHF_Tx SetValue Model.Modulator.AutoScaleBandwidth True

# --- Ground receiver at AGI_HQ ---
New / */Facility/AGI_HQ/Sensor GndSensor
Point */Facility/AGI_HQ/Sensor/GndSensor Targeted Tracking Satellite/STKSat Hold
New / */Facility/AGI_HQ/Sensor/GndSensor/Receiver Gnd_UHF_Rx
Receiver */Facility/AGI_HQ/Sensor/GndSensor/Receiver/Gnd_UHF_Rx SetValue Model Simple_Receiver_Model
# G/T in dB/K; auto-track downlink frequency
Receiver */Facility/AGI_HQ/Sensor/GndSensor/Receiver/Gnd_UHF_Rx SetValue Model.GOverT 6
Receiver */Facility/AGI_HQ/Sensor/GndSensor/Receiver/Gnd_UHF_Rx SetValue Model.FrequencyAutoTracking True

# ============================================================
# UPLINK  145 MHz VHF : AGI_HQ transmitter -> STKSat receiver
# ============================================================
New / */Facility/AGI_HQ/Sensor/GndSensor/Transmitter Gnd_VHF_Tx
Transmitter */Facility/AGI_HQ/Sensor/GndSensor/Transmitter/Gnd_VHF_Tx SetValue Model Complex_Transmitter_Model
Transmitter */Facility/AGI_HQ/Sensor/GndSensor/Transmitter/Gnd_VHF_Tx SetValue Model.Frequency 145000000.0
Transmitter */Facility/AGI_HQ/Sensor/GndSensor/Transmitter/Gnd_VHF_Tx SetValue Model.AntennaControl.Antenna.DesignFrequency 145000000.0
# Ground station out-powers the spacecraft by ~an order of magnitude
Transmitter */Facility/AGI_HQ/Sensor/GndSensor/Transmitter/Gnd_VHF_Tx SetValue Model.Power 25.0
Transmitter */Facility/AGI_HQ/Sensor/GndSensor/Transmitter/Gnd_VHF_Tx SetValue Model.DataRate 9600.0
Transmitter */Facility/AGI_HQ/Sensor/GndSensor/Transmitter/Gnd_VHF_Tx SetValue Model.Modulator BPSK

New / */Satellite/STKSat/Sensor/CommSensor/Receiver Sat_VHF_Rx
Receiver */Satellite/STKSat/Sensor/CommSensor/Receiver/Sat_VHF_Rx SetValue Model Simple_Receiver_Model
# Model.GOverT is a LINEAR ratio (units/K), not dB:  -10 dB -> 0.1
Receiver */Satellite/STKSat/Sensor/CommSensor/Receiver/Sat_VHF_Rx SetValue Model.GOverT 0.1
Receiver */Satellite/STKSat/Sensor/CommSensor/Receiver/Sat_VHF_Rx SetValue Model.FrequencyAutoTracking True

# ============================================================
# RF Environment: rain + atmospheric absorption (ITU-R)
# ============================================================
RFEnvironment * SetValue PropagationChannel.UseRainModel true
RFEnvironment * SetValue PropagationChannel.RainModel ITU-R_P618-13
RFEnvironment * SetValue PropagationChannel.RainModel.SurfaceTemperature 20.6 degC
RFEnvironment * SetValue PropagationChannel.UseAtmosAbsorptionModel true
RFEnvironment * SetValue PropagationChannel.AtmosAbsorptionModel ITU-R_P676-13
RFEnvironment * SetValue PropagationChannel.AtmosAbsorptionModel.UseApproxMethod false

# ============================================================
# Link budget reports (verify margin >= 3 dB)
# ============================================================
# Downlink (UHF): satellite Tx -> ground Rx
ReportCreate */Facility/AGI_HQ/Sensor/GndSensor/Receiver/Gnd_UHF_Rx Type Display Style "Link Budget - Detailed" AccessObject */Satellite/STKSat/Sensor/CommSensor/Transmitter/Sat_UHF_Tx

# Uplink (VHF): ground Tx -> satellite Rx
ReportCreate */Satellite/STKSat/Sensor/CommSensor/Receiver/Sat_VHF_Rx Type Display Style "Link Budget - Detailed" AccessObject */Facility/AGI_HQ/Sensor/GndSensor/Transmitter/Gnd_VHF_Tx

# Access windows (ground station passes)
Access */Satellite/STKSat */Facility/AGI_HQ TimePeriod UseScenarioInterval

# Dump computed transmitter/receiver parameters
Transmitter_RM */Satellite/STKSat/Sensor/CommSensor/Transmitter/Sat_UHF_Tx GetValue
Receiver_RM */Facility/AGI_HQ/Sensor/GndSensor/Receiver/Gnd_UHF_Rx GetValue
week11_comm/week11_verify.py
# -*- coding: utf-8 -*-
# ================================================================
# Week 11 驗證段 — 證明鏈路預算的每個參數真的進到 STK 了
#
# 為什麼通訊週特別需要這一段:
#   鏈路預算是一串乘法。任何一個參數靜默沒生效(功率、頻率、天線增益、
#   G/T、雜訊溫度),Link Budget 報告照樣會印出一個「看起來很合理」的
#   餘裕值 —— 而且是錯的。ACK 不會告訴你這件事。
#
#   本週腳本原本就有兩個這種錯誤,都是靠回讀才抓到的:
#     Model.DesignFrequency        -> 這個屬性不存在,要寫到
#                                     Model.AntennaControl.Antenna.DesignFrequency
#     Model.GOverT -10             -> G/T 是線性比值不是 dB,-10 會被拒
#
# 執行:
#   1. 開啟 STK 12 桌面版(不能用無介面的 STK Engine)
#   2. python week11_verify.py
#   離開碼 0 = 全部通過,1 = 有項目沒生效
# ================================================================

import os
import sys

# notebook 的 kernel 沒有 __file__。而且啟動 STK Engine 會改變 process 的
# 工作目錄,所以不能事後才靠 os.getcwd() —— notebook 開頭的 bootstrap cell
# 會先把週次目錄釘在 WEEK_DIR。
HERE = (os.path.dirname(os.path.abspath(__file__)) if "__file__" in globals()
        else globals().get("WEEK_DIR") or os.path.abspath(os.getcwd()))
sys.path.insert(0, os.path.join(HERE, os.pardir, "tools"))

from agi.stk12.stkdesktop import STKDesktop
from stk_verify import Verifier, run_script                      # noqa: E402

REPO   = os.path.abspath(os.path.join(HERE, os.pardir))
PREREQ = os.path.join(REPO, "week04_orbit", "OrbitDesignLab_Connect.txt")
SCRIPT = os.path.join(HERE, "STKSat_Comm_Connect.txt")

SAT = "*/Satellite/STKSat/Sensor/CommSensor"
GND = "*/Facility/AGI_HQ/Sensor/GndSensor"


def main():
    app = STKDesktop.AttachToApplication()   # 保留 app 參考,否則 COM 介面會失效
    root = app.Root
    if root.CurrentScenario is not None:
        root.CloseScenario()

    # STKSat_Comm_Connect.txt 沒有自己的 New / Scenario,要接在 Lab1 的場景上
    print("--- 前置 ---")
    run_script(root, PREREQ, echo_fail=False)
    print("--- 本週腳本 ---")
    ack, nack = run_script(root, SCRIPT)

    v = Verifier(root)

    # (1) 自動:把腳本設過的每一個 SetValue 讀回來比對
    v.auto_setvalues(SCRIPT)

    # (2) 手動:結構與幾何 —— 這些沒有 SetValue,但錯了鏈路一樣是假的
    v.object_exists("*/Facility/AGI_HQ")
    v.position("*/Facility/AGI_HQ", 40.0416, -75.5983)
    v.elevation_constraint("*/Facility/AGI_HQ", 5.0)
    v.object_exists(SAT)
    v.object_exists(GND)
    v.object_exists(SAT + "/Transmitter/Sat_UHF_Tx")
    v.object_exists(SAT + "/Receiver/Sat_VHF_Rx")
    v.object_exists(GND + "/Transmitter/Gnd_VHF_Tx")
    v.object_exists(GND + "/Receiver/Gnd_UHF_Rx")

    # (3) 自訂:鏈路真的算得出餘裕嗎?
    #     光看檔案「有沒有產生」不夠 —— 空檔案也會產生。要確認裡面
    #     真的有數值列,否則就是另一種靜默失敗。
    out = os.path.join(HERE, "week11_linkbudget.txt")
    if os.path.exists(out):
        os.remove(out)
    try:
        root.ExecuteCommand(
            "ReportCreate " + GND + "/Receiver/Gnd_UHF_Rx Type Save "
            'Style "Link Budget - Detailed" '
            'File "' + out + '" '
            "AccessObject " + SAT + "/Transmitter/Sat_UHF_Tx")
    except Exception as e:
        v.custom("下行 Link Budget 報告", False, str(e)[:60])
    else:
        size = os.path.getsize(out) if os.path.exists(out) else 0
        ebno = []
        if size:
            with open(out, encoding="utf-8", errors="ignore") as f:
                for line in f:
                    p = line.split()
                    # 日期佔 4 個 token,整列共 28 欄;倒數第二欄是 Eb/No (dB)
                    if len(p) == 28 and p[0][:1].isdigit():
                        try:
                            ebno.append(float(p[-2]))
                        except ValueError:
                            pass
        v.custom("下行 Link Budget 有資料列", bool(ebno), f"{size} bytes,{len(ebno)} 列")

        # 手冊要求「餘裕 >= 3 dB」。BPSK 在 BER 1e-5 的門檻 Eb/No 約 9.6 dB,
        # 餘裕 = 實際 Eb/No - 門檻。這是本週真正的 pass/fail 判準,
        # 而它在原本的腳本裡從來沒有被檢查過(只是把報告丟到畫面上)。
        if ebno:
            req, need = 9.6, 3.0
            worst = min(ebno) - req
            v.custom(f"最差鏈路餘裕 >= {need:.0f} dB", worst >= need,
                     f"最差 Eb/No {min(ebno):.2f} dB - 門檻 {req} dB = 餘裕 {worst:.2f} dB")

    ok = v.report("Week 11 驗證段")
    print(f"\n對照:腳本 {ack} 條 ACK / {nack} 條 NACK —— "
          f"但 ACK 數字本身不能證明任何一個參數生效。")
    return 0 if ok else 1


if __name__ == "__main__":
    _rc = main()
    # 在 notebook 裡不要用 SystemExit 打斷 kernel;在終端機才回傳離開碼
    if "ipykernel" not in sys.modules:
        raise SystemExit(_rc)