Week 7 — 功率系統
week07_power/基礎知識導論
Week 7 基礎知識導論 — 電力系統:發電、儲能與收支
電力是衛星唯一不能中斷的資源。任何子系統失效通常還救得回來,
電池放光了,衛星就永遠關機。功率預算是整個任務的硬底線。
1. 這一週在解什麼問題
FIREYE 的太陽能板一圈能發多少電?扣掉地影、扣掉 cosine loss、扣掉衰減之後,
還撐不撐得住 CONOPS 裡的成像與下傳?
電池要多大,才能兩年後還活著?
本週用 STK 的 Solar Panel Tool 算真實幾何下的發電曲線,
再用 Python 對這條曲線積分,做成收支。
2. 核心觀念
2.1 EPS 的四個組成
太陽能板 → 功率調節 (DET / MPPT) → 電池 → 配電 (PDU) → 各負載
| 元件 | 決定什麼 |
|---|---|
| 太陽能板 | 發電上限 |
| 功率調節 | 能不能把板子的功率真的取出來 |
| 電池 | 地影能不能撐過、壽命多長 |
| 配電 | 保護、開關、遙測 |
2.2 發電量:一連串的折扣
P = S * A * η * cos(θ) * F_pack * F_deg * F_temp
| 因子 | 意義 | 典型值 |
|---|---|---|
S |
太陽常數 | 1361 W/m²(地球附近,隨日地距 ±3.4%) |
A |
板面積 | 6U 側板約 0.02 m²/面 |
η |
電池效率 | 三接面 GaAs 約 0.28–0.32 |
cos(θ) |
入射角餘弦 | 這是本週的重點 |
F_pack |
封裝係數(有效面積佔比) | 0.85–0.90 |
F_deg |
壽命衰減(輻射) | 每年約 2–3% |
F_temp |
溫度修正 | 高溫時 Voc 下降,效率降低 |
cos(θ) 是本週的核心。 它由姿態與軌道幾何共同決定,不是常數,
是一條隨時間變動的曲線 —— 這就是為什麼要用 STK 算,不能手估。
2.3 為什麼構型比效率重要
同樣的電池效率,不同的板子配置差距巨大:
| 構型 | 特性 |
|---|---|
| 六面貼板 (body-mounted) | 任何姿態都有部分發電,但平均 cos(θ) 很低 |
| 單面朝日 (+X) | 姿態對時發電最大,姿態錯了幾乎歸零 |
| 展開式 (deployable) | 面積倍增,但增加機構風險與慣性矩(影響 Week 8) |
| 對日追蹤 | 發電最佳,但需要驅動機構與更複雜的 ADCS |
本週要比較 FIREYE(標準)vs FIREYEPosX(+X 面加強) 兩種構型,
用 STK 算出來的發電曲線做定量比較,而不是憑感覺。
2.4 能量收支必須對時間積分
一圈的能量平衡:
∫ P_gen(t) dt ≥ ∫ P_load(t) dt / η_batt_round_trip
η_batt_round_trip充放電來回效率,鋰電池約 0.90–0.95- 只有日照段在發電,但負載整圈都在耗電
- 峰值負載(成像 + 下傳同時)可能遠大於平均值
收支要看兩個層次:一圈平衡(能不能持續),與瞬時餘裕(電池夠不夠深)。
2.5 電池容量:壽命才是驅動條件
一個常見的錯誤推導:
錯: 容量 = 地影時間 x 平均負載 → 得到一個很小的電池
正確的推導要考慮放電深度 (DOD) 與循環壽命:
容量 = (地影能量需求) / (DOD_allow x η_disc)
LEO 兩年約 1 萬次循環。鋰離子要撐 1 萬次,單圈 DOD 通常要壓在 10–20%。
於是電池容量會變成「地影需求」的 5–10 倍。
還要留 EOL(壽命末期)餘裕:電池容量會隨循環衰減,設計要用 EOL 容量,不是 BOL。
2.6 DET vs MPPT
| 架構 | 原理 | 適用 |
|---|---|---|
| DET (Direct Energy Transfer) | 板子直接接匯流排,多餘功率被分流掉 | 簡單、可靠、功率小 |
| MPPT (Max Power Point Tracking) | 追蹤最大功率點 | 效率高 5–20%,尤其溫度變化大時 |
CubeSat 現在多用 MPPT,但 MPPT 本身也耗電、也會失效。
這是一個典型的「效率 vs 複雜度」權衡。
2.7 STK Solar Panel Tool 在做什麼
它做的是幾何與遮蔽的計算:
- 依姿態與太陽方向算每一面板的入射角
- 判斷是否在地影(以及半影)
- 計算自我遮蔽(其他結構擋住板子)
- 輸出功率 vs 時間
它不做:電池模型、MPPT 效率、溫度修正。
那些是你在 Python 端要自己補上的 —— 這正是本課「STK 主導、Python 補位」的分工。
本教材踩過的坑:Week 4 腳本裡的 SolarPanel DeleteData 會把資料清掉,
導致 Week 7 的發電報告變成 57 bytes 的空檔案 —— 而且沒有任何錯誤訊息。
這是「ACK 不等於生效」的典型案例,現已在兩支腳本裡註解掉。
3. 名詞與符號
| 符號 | 意義 | 單位 | 典型值 |
|---|---|---|---|
S |
太陽常數 | W/m² | 1361 |
η |
電池轉換效率 | — | 0.28–0.32 |
θ |
太陽入射角 | ° | 隨時間變 |
| DOD | 放電深度 | % | 每圈 10–20% |
| BOL / EOL | 壽命初期 / 末期 | — | 設計用 EOL |
| DET / MPPT | 功率調節架構 | — | 見 2.6 |
Voc / Isc |
開路電壓 / 短路電流 | V / A | 隨溫度變 |
W·hr |
能量單位 | — | 電池容量常用 |
4. 觀念 → 本週實驗對照
| 觀念 | 出現在本週的哪裡 |
|---|---|
| 幾何發電曲線 | FIREYE_SolarPanel_Connect.txt,STK Solar Panel Tool |
| 構型比較 | FIREYE vs FIREYEPosX |
| 能量積分與收支 | tools/storage_vs_time.py 的積分工具 |
| 選型比較 | 三種太陽能板 / 電池組合 |
| 空檔案的靜默失敗 | week07_verify.py 檢查報告是否真的有資料列 |
注意:
week07_verify.py目前有一項刻意保留的 FAIL ——
發電報告的 Power 欄從某一列起變成「距分析起點的經過秒數」。
這個輸出異常尚未查明,教材選擇把它做成具名的檢查項顯示出來,而不是隱藏它。
這本身就是一個教學點:驗證段的價值在於讓未知變成可見的未知。
5. 常見誤解
- 「板子面積乘效率就是發電量。」 — 少了
cos(θ)、封裝、衰減、溫度,實際值可能只有一半。 - 「電池容量 = 地影耗電量。」 — 見 2.5,壽命(DOD 限制)才是驅動條件,差 5–10 倍。
- 「太陽能板效率愈高愈好。」 — 效率高的電池溫度係數與成本也不同,
而且構型的影響常常大於效率的影響。 - 「發電曲線是平滑的正弦。」 — 有自我遮蔽、有半影、有姿態機動,真實曲線很不規則。
這是必須用 STK 而不能手算的原因。 - 「報告檔產生了就是算好了。」 — 空檔案也會產生。要檢查裡面真的有數值列。
6. 延伸閱讀
電力系統
- NASA State of the Art of Small Spacecraft Technology
— Power 一章,市售 CubeSat 太陽能板、電池、EPS 板的實際規格與效率。 - NASA CubeSat 101(PDF)
— 功率預算的入門作法。 - NASA Technical Reports Server (NTRS)
— 搜尋 "CubeSat electrical power system" 或 "solar array degradation"。 - ECSS 標準總表 — ECSS-E-ST-20(電力與電磁)。
工具
- Ansys STK 線上說明
— Solar Panel Tool 的幾何模型與限制。本機說明檔在
C:\Program Files\AGI\STK 12\Help\Content\。 - SPENVIS(ESA) — 可估算軌道輻射劑量與太陽電池衰減。
- NumPy / SciPy
— 對發電曲線做積分與內插。
7. 課前自我檢核
- 六面貼板的 6U CubeSat,任一時刻大約有幾面在發電?平均
cos(θ)大概多少? - 電池只按「地影耗電量」設計,兩年後會發生什麼事?請用循環壽命解釋。
- FIREYE 與 FIREYEPosX 哪一個發電多?在什麼姿態假設下這個答案會反過來?
- 若發電報告只有 57 bytes,你怎麼在程式裡自動偵測這件事?
- 你的功率餘裕用的是 BOL 還是 EOL 的電池容量?差多少?
圖

week07_power/storage_compare_demo.png

week07_power/storage_vs_time.png
程式碼
# ============================================================
# FIREYE Solar Panel power generation - STK native (VO SolarPanel)
# Reproduces the SmallSat/eSatellite FIREYE vs FIREYEPosX
# configuration comparison WITHOUT SOLIS.
#
# Reference: help.agi.com/stk .../Content/cmd_VOSolarPanel.htm (v13)
#
# CRITICAL PREREQUISITE - panel geometry lives in the 3D MODEL:
# VO SolarPanel can only Compute over panel GROUPS that are
# already defined inside the satellite's model file
# (.mdl / .glb), each as "SolarPanelGroup <Name> <Efficiency%>".
# Connect CANNOT create a panel face or change its normal.
# => The "3-face vs 5-face" comparison = two different models
# (or two group selections), exactly like FIREYE vs FIREYEPosX.
#
# HOW TO PREP THE MODEL (one-time, in the model file):
# Add SolarPanelGroup lines naming each face + efficiency, e.g.
# SolarPanelGroup PosX 30.0
# SolarPanelGroup NegX 30.0
# SolarPanelGroup PosY 30.0
# SolarPanelGroup NegY 30.0
# SolarPanelGroup PosZ 30.0 <- only in the "PosX" (5-face) variant
# (0.30 efficiency from the tutorial Solar Cell Type.)
#
# Output units: Power = scenario power unit (set below), plus
# solar intensity 0..1 per step.
# ============================================================
# ---- Scenario power unit -> Watts (so the report reads in W) ----
Units_Set * Report Power W
SetUnits / km
# ============================================================
# CONFIG A - FIREYE (imager on +Z / nadir; NO +Z panel = "5-face
# minus nadir" per tutorial: panels on all sides except imager face)
# Assumes model FIREYE has groups: PosX NegX PosY NegY NegZ
# ============================================================
# (Assumes */Satellite/FIREYE already exists from eSatellite_STK_Connect.txt)
# Optionally show the solar-panel 3D view while computing:
# VO */Satellite/FIREYE SolarPanel Visualization Radius On 60 AddGroup PosX AddGroup NegX AddGroup PosY AddGroup NegY AddGroup NegZ View On
# Add Earth as an obstruction so eclipse is modeled:
VO */Satellite/FIREYE SolarPanel Object On CentralBody/Earth
# Compute POWER over ~2 orbits (700 km SSO ~ 98.8 min) at 60 s steps.
# {TimeInterval} <StepSize> {ReportType} "<FilePath>"
VO */Satellite/FIREYE SolarPanel Compute "UseAnalysisStartTime" "UseAnalysisStopTime" 60 Power "C:\temp\FIREYE_power.txt"
# (Optional) illuminated AREA vs time:
# VO */Satellite/FIREYE SolarPanel Compute "UseAnalysisStartTime" "UseAnalysisStopTime" 60 Area "C:\temp\FIREYE_area.txt"
# Free the illumination data when done:
# DeleteData 會清掉太陽能板的計算狀態,之後即使重新 Object On,
# 下一次 Compute 也只會產出空報告(而且照樣花兩分鐘、照樣 ACK)。
# Week 7 接在本檔之後,正是被這一行害的 —— 報告已經寫出來了,
# 這行沒有實際用途,註解掉。
#VO */Satellite/FIREYE SolarPanel DeleteData
# ============================================================
# CONFIG B - FIREYEPosX (imager moved to +X; +Z now carries a panel;
# tutorial removes the +X panel and ADDS a +Z panel)
# Assumes model FIREYEPosX has groups: NegX PosY NegY NegZ PosZ
# ============================================================
# (Assumes */Satellite/FIREYEPosX exists - a 2nd satellite using the
# PosX-configuration model. Create it the same way as FIREYE but
# load the FIREYEPosX model and set the imager/SBand on +X.)
# NOTE: no script in this course creates FIREYEPosX, so these three lines
# fail unless you build that second satellite first (see the block above).
# Uncomment once */Satellite/FIREYEPosX exists with the PosX-configuration model.
#VO */Satellite/FIREYEPosX SolarPanel Object On CentralBody/Earth
#VO */Satellite/FIREYEPosX SolarPanel Compute "UseAnalysisStartTime" "UseAnalysisStopTime" 60 Power "C:\temp\FIREYEPosX_power.txt"
#VO */Satellite/FIREYEPosX SolarPanel DeleteData
# ============================================================
# Compare: plot FIREYE_power.txt vs FIREYEPosX_power.txt
# (avg power, eclipse depth, whether generation >= load).
# The Python script storage_vs_time.py can also ingest these.
# ============================================================
# -*- coding: utf-8 -*-
# ================================================================
# Week 7 驗證段 — 太陽能板發電計算真的產出資料了嗎
#
# VO SolarPanel Compute 是本週的核心,而它有兩個靜默失敗的入口:
# 一是模型裡沒有 SolarPanelGroup 定義(算不出東西但照樣 ACK),
# 二是輸出檔寫不出來(資料夾不存在時 Connect 靜默失敗)。
#
# 執行:python week07_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()))
REPO = os.path.abspath(os.path.join(HERE, os.pardir))
sys.path.insert(0, os.path.join(REPO, "tools"))
from stk_verify import Verifier, load_lab
from agi.stk12.stkdesktop import STKDesktop
from stk_verify import run_script # noqa: E402
def main():
app = STKDesktop.AttachToApplication()
root = app.Root
if root.CurrentScenario is not None:
root.CloseScenario()
# 本週沒有自己的 New / Scenario,接在 Week 4 的完整場景上
run_script(root, os.path.join(REPO, "week04_orbit",
"FIREYE_Volcano_FullScenario_Connect.txt"), echo_fail=False)
ack, nack = run_script(root, os.path.join(HERE, "FIREYE_SolarPanel_Connect.txt"))
v = Verifier(root)
v.object_exists("*/Satellite/FIREYE")
v.expect_true("腳本沒有 NACK", nack == 0, f"{ack} ACK / {nack} NACK")
# 發電報告必須有資料列,而且功率值要落在物理上合理的範圍
out = os.path.join("C:" + os.sep, "temp", "FIREYE_power.txt")
# 報告每列是: 1 Jan 2024 00:01:00.000 <Power W> <Solar Intensity>
# 日期佔 4 個 token,所以 Power 是倒數第二欄(不是最後一欄)
vals = []
if os.path.exists(out):
for line in open(out, encoding="utf-8", errors="ignore"):
p = line.split()
if len(p) == 6 and p[0].isdigit():
try:
vals.append(float(p[-2]))
except ValueError:
pass
v.expect_true("發電報告有資料列", len(vals) > 100, f"{len(vals)} 列")
if vals:
import numpy as np
a = np.array(vals)
v.expect_true("有地影期(功率 = 0)", a.min() <= 1e-9,
f"最小 {a.min():.3f} —— 沒有 0 就表示地影沒被算進去")
# 真實發電功率會隨軌道週期性振盪(日照 -> 地影 -> 日照)。
# 若整段單調遞增,表示這一欄根本不是功率。
# 已知問題:本機 STK 12.10.0 的輸出從某一列起,Power 欄的值
# 等於「距分析起點的經過秒數」(例如 7 Jan 22:38 那列是 599880),
# 顯然不是瓦特。這一項會 FAIL —— 它是刻意留著的,用來標記這個
# 尚未查明的輸出異常,而不是假裝沒看到。
rising = float(np.mean(np.diff(a) > 0))
v.expect_true("功率隨軌道振盪(不是單調遞增)", rising < 0.9,
f"{rising*100:.1f}% 的相鄰點在上升;"
f"最大值 {a.max():.0f} —— 若接近分析窗秒數就是欄位錯亂")
v.expect_between("功率峰值的量級(6U CubeSat 合理範圍 1~60 W)",
float(np.percentile(a[a > 0], 95)) if (a > 0).any() else 0, 1, 60)
ok = v.report("Week 7 驗證段")
return 0 if ok else 1
if __name__ == "__main__":
_rc = main()
if "ipykernel" not in sys.modules:
raise SystemExit(_rc)