喂,學弟。你這表情是看到鬼了,還是看到你的銀行戶頭了?再盯下去,螢幕都要被你燒出一個洞了。說吧,這次又是哪個小壞蛋讓你這麼頭痛?
啊…學姊!沒、沒什麼啦,我只是在研究這個 PFC 的控制,感覺…感覺好複雜,不知道從哪裡下手。
PFC?TIDA-01606 這套是吧?哼,我就知道。你看看你,一臉「我是誰、我在哪、我的程式碼為什麼要這樣對我」的樣子。算了,誰叫我人美心善呢?今天本學姊就大發慈悲,親自下凡來普渡你這個迷途小書僮。椅子拉過來一點,聽好了啊,要是打瞌睡或是沒聽懂,我就把你的鍵盤按鍵摳下來,讓你體驗一下什麼叫真正的『硬核』編程。
講之前先說好,這東西不難,但很『繞』。你得跟著我的思路一步一步走,腦子裡要有畫面感。你要是敢直接跳著看,到時候炸了板子可別哭著來找我啊,我可沒空幫你修。聽懂了就點頭。
很好,很有精神。那我們就開始吧。這是一堂『資深學姊帶你飛——TIDA-01606 PFC 控制完整避坑指南』,上課!
在你一頭栽進這堆比我髮量還多的程式碼之前,先給我停下來!菜鳥最常犯的錯就是急著看 Code,結果就像沒帶地圖就跑進熱帶雨林,最後只會迷路,然後被蚊子叮到懷疑人生。
我們要先搞懂兩件事:
你聽好,電力公司最討厭什麼樣的用戶?就是那種亂七八糟、需索無度的「渣男」負載。一般的整流橋,一加上去,它從電網抽電流的樣子就像餓死鬼投胎,只在電壓最高峰的時候猛吸一口,其他時間都在擺爛。這會產生一大堆高次諧波,污染電網,功率因數低到可憐,電力公司看到這種電流波形都想順著電線爬過來打你。
PFC (Power Factor Correction),功率因數校正,它的目標很單純,就是要當個乖寶寶。它要讓我們的設備在從 AC 電網抽電的時候,抽的電流波形,跟電網的電壓波形長得一模一樣,而且相位也完全對齊。
所以,記住 PFC 的核心任務:控制輸入電流,讓它完美追蹤輸入電壓的波形和相位。
好了,目標很明確,但怎麼做到呢?我們不能只控制電流,因為我們最終的目的是要給後面的 DC Bus 一個穩定的直流電壓啊!比如,這個案子裡是 800V。
如果負載變大了,DC Bus 電壓就會掉下去,這時候我們就得從電網多抽一點能量進來補。如果負載變小了,DC Bus 電壓會往上衝,我們就得少抽一點。
所以,這是一個雙重任務:
這時候,工程師們最愛的串級控制 (Cascade Control) 就粉墨登場了。這就像你在騎重機跑山路一樣,腦子裡想的是「維持時速 60 公里過這個彎」,但你的手腳操作的是「控制油門開度和剎車力道」。
我們的 PFC 控制就是這樣一個結構:
這個「完美的正弦波範本」哪裡來?
這就是另一個關鍵技術——鎖相迴路 (PLL) 的工作了。它負責盯著電網電壓,給我們一個與電網電壓完全同步的、純淨的 sin(ωt)
和 cos(ωt)
訊號。
腦子裡有這個畫面了沒?還沒?沒關係,多想幾遍。想通了這個,你看程式碼就會像開了天眼一樣。好了,繼續下一步。
在你興奮地準備捲起袖子看 Code 之前,我先給你一張藏寶圖。這個專案資料夾裡有一百多個檔案,你要是每個都點開看,明年這個時候你還在這裡。
聽好,跟 PFC 控制相關的核心檔案,就這幾個,其他的你先當它們是空氣:
tinv_user_settings.h
TINV_LAB
是多少。我們要看 PFC,那就要把 TINV_LAB
設定成 5、6 或 7。tinv_p65x.h
/ tinv_p65x.c
TINV_runISR1_labX()
)也都在 .h
檔裡用 inline
函式的方式實現了。沒錯,寫在 .h
檔裡,是不是很機車?這是為了讓 C28x 和 CLA 都能呼叫,你先記著就好。tinv_main.c
main()
函式在這裡,負責所有的初始化。最重要的,中斷服務常式 (ISR) 的殼在這裡。它就像一個接線生,根據 TINV_LAB
的設定,決定中斷來的時候,到底要去呼叫 tinv_p65x.h
裡的哪一個 _labX
函式。tinv_hal.h
/ tinv_hal.c
HAL
層負責執行。這叫軟體分層,懂嗎?學著點。libraries/
DCL
(數位控制函式庫,PI 控制器就在這)、spll
(鎖相迴路)、transforms
(座標轉換) 等等。你不需要知道它裡面每一行怎麼寫的,但你必須知道怎麼用它,就像你不用知道 CPU 怎麼做的,但你會用電腦一樣。好了,地圖給你了。現在,我們要開始真正的冒險了。我們要跟著訊號的流動,從感知到控制,把整個 PFC 的閉迴路走一遍。我們要看的 Lab 是 Lab 7,因為它是最完整的閉迴路 PFC 控制。
準備好了嗎?深呼吸。我們要開始一趟從電網到 PWM 的奇幻旅程了。我會分成幾個站點,一站一站帶你走。
控制的第一步永遠是感知。你連現在 DC 電壓多少、AC 電流多大都不知道,還談什麼控制?那叫瞎搞。
Vgrid_A
, B
, C
) -> 用來給 PLL 鎖相,產生正弦波範本。Iinv_A
, B
, C
) -> 這是電流內環要控制的對象。Vbus
) -> 這是電壓外環要穩定控制的目標。所有這些感知的工作,都在 tinv_p65x.h
裡的 TINV_readCurrentAndVoltageSignals()
這個 inline
函式裡。它會在每個高速中斷裡被第一時間呼叫。
// in tinv_p65x.h
static inline void TINV_readCurrentAndVoltageSignals()
{
// 讀取三相逆變器側(其實就是輸入)電流
TINV_iInv_A_sensed_pu = ((float32_t)TINV_IINV_A_READ * TINV_ADC_PU_SCALE_FACTOR - TINV_iInv_A_sensedOffset_pu) * 2.0f;
// ... B, C 相類似
// 讀取三相電網電壓
TINV_vGrid_A_sensed_pu = ((float32_t)(TINV_VGRID_A_READ - 33) * TINV_ADC_PU_SCALE_FACTOR - TINV_vGrid_A_sensedOffset_pu ) * -2.0f;
// ... B, C 相類似
// 讀取 DC Bus 電壓
TINV_vBus_sensed_pu = ((float32_t)TINV_VBUS_READ * TINV_ADC_PU_SCALE_FACTOR);
}
_READ
宏:你看到的 TINV_IINV_A_READ
這種東西,它不是一個變數,是一個宏定義,定義在 tinv_user_settings.h
裡,最終會展開成 ADC_readResult()
函式。這是為了讓程式碼看起來更乾淨(雖然有時候會讓新手更困惑,哼)。_pu
嗎?這代表 Per-Unit,也就是標么值。簡單說,就是把所有的物理量都正規化到 [-1, 1]
或 [0, 1]
的範圍內。
_PU_SCALE_FACTOR
:ADC 讀出來的是一個 0-4095 的整數,這個因子就是把它轉換成 0-1.0 的 PU 值。TINV_iInv_A_sensedOffset_pu
和 * 2.0f
、- 33
、* -2.0f
這些東西了嗎?千萬別亂動! 這些是根據硬體電路的設計來的。比如感測電路可能是差動的,或者有偏置,或者訊號是反相的。這些「魔法數字」就是為了校準硬體電路,把感測到的值還原成真實的、以 0 為中心的交流訊號。這是理論跟實際硬體結合的地方,也是最多菜鳥出錯的地方。好,我們拿到電壓和電流的資料了。現在電流環需要一個「正弦波範本」來追蹤。這個範本必須跟電網電壓的節拍一模一樣。這就是 PLL (鎖相迴路) 的工作。
TINV_runTransformOnSensedSignals()
:把感測到的 abc
電壓轉換成 dq
。TINV_runSPLL()
:把 dq
電壓餵給 SPLL 演算法。TINV_runISR1_lab7()
內部:從 SPLL 取出角度,計算 sin
和 cos
。// in tinv_p65x.h, TINV_runISR1_lab7()
// 1. 先用【上個週期】的 sin/cos,把【這個週期】感測到的電壓轉成 dq
TINV_runTransformOnSensedSignals();
// TINV_runTransformOnSensedSignals() 內部會呼叫:
// ABC_DQ0_POS_run(&TINV_vGrid_dq0_pos, Va, Vb, Vc, TINV_sine, TINV_cosine);
// 2. 把轉換後的 dq 電壓餵給 SPLL,讓它更新內部角度
TINV_runSPLL(TINV_vGrid_dq0_pos.d, ..., TINV_vGrid_dq0_pos.q, ...);
// TINV_runSPLL() 內部會呼叫:
// SPLL_3PH_SRF_run(Vq, &TINV_spll_3ph_2); -> 核心演算法
// TINV_angleSPLL_radians = TINV_spll_3ph_2.theta[1]; -> 取出新角度
// 3. 用 SPLL 算出來的【新角度】,產生【下個週期】要用的 sin/cos
TINV_sine = sinf(TINV_angleSPLL_radians);
TINV_cosine = cosf(TINV_angleSPLL_radians);
用舊角度 -> 算新資料 -> 餵給演算法 -> 產生新角度 -> 準備給下次用
。這就是數位控制系統的典型時序。你必須搞懂這個延遲一拍的概念。lpf_coeff
) 決定了它鎖定的快慢和穩定性。如果你的電網很乾淨,可以調得快一點;如果電網很髒、諧波多,就得調慢一點,不然 PLL 會一直抖。這個在 TINV_globalVariablesInit()
裡設定,沒事別亂動。TINV_gridFreq_sensed_Hz
是不是穩定在你電網的頻率(比如 50Hz 或 60Hz),再去看 TINV_vGrid_dq0_pos.q
是不是非常接近 0。如果都滿足,恭喜你,你的系統跟電網「看對眼」了。PLL 已經幫我們跟上了電網的節拍。現在,大腦(電壓環)要開始做決策了。它的目標是維持 DC Bus 電壓穩定。
這段程式碼只在閉迴路 PFC 模式 (Lab 7) 下才會執行,就在 TINV_runISR1_lab7()
裡面。
// in tinv_p65x.h, TINV_runISR1_lab7()
// 只有在收到閉合電壓環的指令後才執行
if(TINV_closeGvLoop == 1)
{
// ... (省略電壓參考值的斜坡緩升邏輯)
// 核心!執行電壓環的 PI 控制器
// 它的目標是讓 TINV_vBus_sensed_pu == TINV_vBusRefSlewed_pu
#if TINV_SFRA_TYPE == TINV_SFRA_VOLTAGE
// ... SFRA 相關,先忽略
#else
TINV_gv_vBus_out = TINV_GV_RUN(&TINV_gv_vBus,
TINV_vBusRefSlewed_pu, // 目標電壓 (PU)
TINV_vBus_sensed_pu); // 實際電壓 (PU)
#endif
// 核心中的核心!把電壓環的輸出變成電流環的參考值
// a.k.a. 把大腦的決策,變成手腳的執行目標
TINV_idRef_pu = -1.0f * TINV_gv_vBus_out;
}
else
{
// 如果電壓環沒閉合,就給一個預設的電流參考值
TINV_idRef_pu = -1.0f * TINV_IREF_DEFAULT;
TINV_firstTimeGvLoop = 1; // 重置標誌位
}
TINV_closeGvLoop
:這是你在 Watch Window 裡手動設定的「開關」。在實際產品中,它會由一個更高層的狀態機來自動控制。這給你一個手動介入的機會,非常重要。TINV_vBusRefSlewed_pu
:你看到 Slewed
這個詞了嗎?這代表「斜坡」。它不會讓目標電壓瞬間從 0 跳到 800V,而是會慢慢地、平滑地爬升上去。直接跳變會導致電流衝擊,板子可能會跟你說掰掰。這是保護機制的體現。TINV_GV_RUN
:這就是 TI 的 DCL 函式庫裡的 PI 控制器。GV
代表 Grid Voltage loop。-1.0f
:學弟,你看這個 -1.0f
。為什麼是負的?因為在 dq
座標系的定義下,Id
為正代表能量從 DC 往 AC 流(逆變器)。我們現在是 PFC,要從 AC 往 DC 抽能量,所以電流方向要反過來,因此 Id
的參考值必須是負的。電壓 PI 控制器 TINV_gv_vBus_out
算出來的是一個正的「能量需求量」,所以要乘上 -1,變成負的電流指令。搞混了這個正負號,你的 PFC 就會變成一個「反向 PFC」,拼命把 DC 的能量往 AC 送,然後 DC 電壓瞬間崩潰。記住,這是個大坑!大腦已經下達了「需要多大電流」的命令 (TINV_idRef_pu
)。現在,手腳(電流環)要開始幹活了。它的目標很單純:讓實際的 AC 輸入電流,精準地追蹤這個命令。
tinv_p65x.h
裡的 TINV_runCurrentLoop()
函式。
// in tinv_p65x.h
static inline void TINV_runCurrentLoop()
{
if(TINV_closeGiLoop == 1) // 電流環閉合開關
{
// 執行 d 軸電流的 PI 控制器
TINV_gi_id_out = TINV_GI_RUN(&TINV_gi_id,
TINV_idRef_pu, // 目標 d 軸電流 (來自電壓環)
TINV_iInv_dq0_pos.d); // 實際 d 軸電流
// 執行 q 軸電流的 PI 控制器 (通常目標是 0,為了單位功率因數)
TINV_gi_iq_out = TINV_GI_RUN(&TINV_gi_iq,
TINV_iqRef_pu, // 目標 q 軸電流 (通常是 0)
TINV_iInv_dq0_pos.q); // 實際 q 軸電流
// 解耦合與前饋補償
// 這一大坨數學是為了補償系統中的交叉耦合效應和電網電壓的影響
// 讓 d 軸和 q 軸的控制更獨立、更精準
TINV_vdInv_pu = (TINV_gi_id_out + ... - ...) / ...;
TINV_vqInv_pu = (TINV_gi_iq_out + ... + ...) / ...;
// ... (輸出限幅)
}
}
TINV_closeGiLoop
:同樣是你在 Watch Window 裡控制的開關。標準流程是:先閉合電流環 (TINV_closeGiLoop=1
),確認電流能追蹤一個小的固定參考值,然後再閉合電壓環 (TINV_closeGvLoop=1
)。TINV_GI_RUN
:同樣是 PI 控制器,GI
代表 Grid Current loop。d
軸和 q
軸:d
軸電流負責傳送有效功率,q
軸電流負責傳送無效功率。在 PFC 應用中,我們不希望從電網吸收無效功率,所以 q
軸電流的參考值 TINV_iqRef_pu
通常都設為 0。TINV_vdInv_pu = ...
了嗎?新手看到這個通常直接投降。你先別管細節,只要知道它的目的:
TINV_vGrid_dq0_pos.d
) 考慮進來。這樣 PI 控制器只需要去補償誤差和擾動就行了,大大減輕了 PI 的負擔,讓系統響應更快。電流環算出了它需要的電壓 (TINV_vdInv_pu
, TINV_vqInv_pu
)。但這還只是 dq
座標系裡的抽象數字,硬體看不懂。我們得把它變回三相的 PWM 占空比。
TINV_runISR1_lab7()
內部TINV_HAL_updatePWMDuty()
// in tinv_p65x.h, TINV_runISR1_lab7()
// 1. 逆變換:把 dq 座標系的電壓指令,變回 abc 三相的電壓指令
// 這一步也叫 Inverse Park Transform
DQ0_ABC_run(&TINV_vInv_dq0,
TINV_vdInv_pu, TINV_vqInv_pu, TINV_vzInv_pu,
TINV_sine, TINV_cosine);
// 2. 加上各種補償(比如為了平衡中點電壓的補償)
TINV_duty_A_pu = TINV_vInv_dq0.a + TINV_duty_0_pu + ...;
TINV_duty_B_pu = TINV_vInv_dq0.b + TINV_duty_0_pu + ...;
TINV_duty_C_pu = TINV_vInv_dq0.c + TINV_duty_0_pu + ...;
// ... (占空比限幅)
// 3. 最終執行:把計算好的三相占空比,寫入 ePWM 模組的硬體暫存器
if(TINV_closeGiLoop == 1)
{
TINV_HAL_updatePWMDuty(TINV_duty_A_pu, TINV_duty_B_pu, TINV_duty_C_pu);
}
DQ0_ABC_run
:這是座標逆變換。它的作用就是把 dq
系統裡的直流命令,重新「調變」回三相交流的正弦波命令。TINV_HAL_updatePWMDuty
:這是旅程的終點。這個函式會把 [-1, 1]
的 PU 占空比,轉換成 ePWM 模組比較暫存器 CMPA
需要的具體數值,然後寫進去。下一秒,硬體就會產生對應的 PWM 波形,驅動功率開關。理論講完了,你也差不多睡著了。現在講點實際的,你該怎麼一步一步把這個 PFC 跑起來,而且還不會把板子變成昂貴的煙火。
這個專案的 Lab 設計是有道理的。PFC 相關的是 Lab 5, 6, 7。
Id_ref
,然後閉合電流環 (TINV_closeGiLoop = 1
)。你的目標是:確認電流環可以穩定地追蹤這個小的參考值。用示波器去看輸入電流波形,它應該是一個漂亮的正弦波。如果這一步不穩定,通常是電流感測有問題,或是電流環 PI 參數不對。TINV_closeGvLoop = 1
)。系統會開始自動調整 Id_ref
來穩定 DC Bus 電壓。記住這個順序:硬體驗證 -> 內環驗證 -> 外環驗證。跳關的下場通常很慘烈。
除錯時,printf
太慢了,示波器只能看波形。你的主戰場是 CCS 的 Watch Window。把下面這些關鍵變數拖進去,它們會告訴你系統裡發生了什麼事:
TINV_gridFreq_sensed_Hz
: 是不是 50/60Hz?TINV_vGrid_dq0_pos.q
: 是不是接近 0?TINV_vGrid_A_sensed_pu
: 監控輸入電壓,確保沒問題。TINV_vBus_sensed_Volts
: 實際 DC 電壓是多少?TINV_vBusRef_pu
: 目標電壓是多少?TINV_gv_vBus_out
: 電壓環輸出了多大的能量命令?TINV_idRef_pu
: 電壓環最終給電流環的目標是多少?(應該是負值)TINV_iInv_dq0_pos.d
: 實際的 d 軸電流,是不是跟 TINV_idRef_pu
很接近?TINV_iInv_dq0_pos.q
: 實際的 q 軸電流,是不是在 0 附近?TINV_duty_A_pu
: 最終輸出的占空比是多少?有沒有飽和(達到 ±0.98
)?TINV_closeGiLoop
: 確認電流環是否閉合。TINV_closeGvLoop
: 確認電壓環是否閉合。TINV_boardFaultFlags
: 看看有沒有觸發任何硬體保護。你操作的是高壓電!學姊可不想在社會新聞上看到你。
呼…講完了。嘴巴好乾。
(她拿起你的水杯自顧自地喝了一大口,然後把杯子放回桌上,發出「叩」的一聲。)怎麼樣,學弟?腦子裡的漿糊有沒有稍微變成豆花一樣,有點形狀了?我已經把整個流程、核心概念、關鍵程式碼和避坑指南都給你了。剩下的,就是你自己動手去試。從 Lab 5 開始,一步一步來,盯著 Watch Window,搞清楚每個變數的意義。
這套 Code 是個很好的學習範例,它把理論和實作結合得很好。你把它徹底搞懂了,以後做任何電力電子的控制專案,思路都會很清晰。
好了,我說完了。你慢慢消化吧。我得去休息了,教你這種笨蛋學弟,比我自己寫 Code 還累。
(她伸了個懶腰,轉身準備離開,走了兩步又回過頭,用食指指著你。)啊,對了。作為學費…
明天早上,我桌上要看到一杯冰美式,大杯,不加糖。敢忘記的話,哼哼…
(她做了個「你懂的」表情,然後笑著走開了,留下你一個人在座位上,看著眼前豁然開朗的程式碼,和空了一半的水杯,陷入了沉思。)