:從收發(fā)鏈路到誤碼率對齊的MATLAB實現(xiàn))
簡介這份資源是一套基于MATLAB實現(xiàn)的OFDMA收發(fā)仿真系統(tǒng)面向通信工程、電子信息類專業(yè)學生及無線通信算法入門研究者用于理解正交頻分多址在4G LTE與5G NR中的復用機制。壓縮包共2個文件包含1個m腳本與1個txt許可說明整體約4KB其中m文件承載核心仿真邏輯txt用于聲明使用與分發(fā)條件。仿真內(nèi)容覆蓋子載波正交性、頻域資源分配、QPSK/16QAM/64QAM調(diào)制與卷積或Turbo編碼、IFFT/FFT變換、同步與信道均衡、接收端信道估計與解調(diào)解碼以及誤碼率、符號錯誤率、吞吐量等性能評估和星座圖、誤碼率曲線可視化。已有213人學習下載適合作為課程設(shè)計或算法驗證的參考腳本幫助讀者在MATLAB環(huán)境下搭建基本OFDMA鏈路并觀察多用戶傳輸效果。1. OFDMA仿真系統(tǒng)從“收發(fā)鏈路跑不通”到“誤碼率曲線能對齊”的那條路做無線通信基帶仿真的工程師幾乎都繞不開 OFDMA 這個詞。它是 Wi-Fi 6、5G NR、電力線通信里都在用的多址方式核心思路是把一段寬帶切成許多互相正交的子載波再按需分配給不同用戶。聽起來不復雜但真正動手寫一套 OFDMA 收發(fā) MATLAB 仿真時很多人會卡在同一個地方單用戶鏈路明明能跑一加多用戶資源映射星座圖就散成一團誤碼率曲線怎么調(diào)都對不上理論值。這套仿真系統(tǒng)要解決的正是從“子載波分配、IFFT/FFT、循環(huán)前綴、信道估計到多用戶解調(diào)”這一整條鏈路的可復現(xiàn)問題。它適合已經(jīng)懂一點 OFDM、想進一步搞清多用戶資源調(diào)度和接收端處理的同學也適合需要快速搭一個驗證平臺、用來測試自己信道估計算法或同步算法的從業(yè)者。下面我按自己搭這套東西的順序把關(guān)鍵環(huán)節(jié)和踩過的坑講清楚。2. 先把 OFDMA 收發(fā)鏈路的骨架搭對子載波分配與 IFFT 的先后關(guān)系2.1 為什么不能直接把 OFDM 代碼改成多用戶很多人上手的第一反應(yīng)是拿一段單用戶 OFDM 代碼把數(shù)據(jù)分成幾份分別做 IFFT再把結(jié)果加起來。這個做法在數(shù)學上等價于把多個用戶的信號在時域疊加但接收端要分離它們就非常困難因為每個用戶經(jīng)歷的 IFFT 是獨立的子載波映射關(guān)系被打亂了。OFDMA 的正確做法是所有用戶在頻域上共享同一個 IFFT 塊每個用戶只占用分配給自己的那部分子載波其余子載波置零。這樣一次 IFFT 就完成了多用戶復用接收端做一次 FFT 后按同樣的子載波分配表就能把各用戶的數(shù)據(jù)取出來。這個順序不能反反了后面所有同步和估計都會對不上。2.2 資源映射的最小可運行代碼下面這段代碼演示了 4 個用戶、總共 64 個子載波、每個用戶占 16 個連續(xù)子載波的資源映射過程。實際系統(tǒng)里可能是交織分配但連續(xù)分配更容易看清映射邏輯。% 參數(shù)設(shè)置 N_fft 64; % FFT 點數(shù) N_cp 16; % 循環(huán)前綴長度 N_user 4; % 用戶數(shù) N_sc_per_user N_fft / N_user; % 每用戶子載波數(shù) mod_order 4; % QPSK bits_per_sym log2(mod_order); % 生成每個用戶的隨機比特并調(diào)制 user_data cell(1, N_user); user_sym cell(1, N_user); for u 1:N_user n_bits N_sc_per_user * bits_per_sym; user_data{u} randi([0 1], n_bits, 1); % 比特轉(zhuǎn)整數(shù)再 QPSK 調(diào)制 int_data bi2de(reshape(user_data{u}, bits_per_sym, [])., left-msb); user_sym{u} pskmod(int_data, mod_order, pi/4); end % 頻域資源映射每個用戶占用一段連續(xù)子載波 freq_grid zeros(N_fft, 1); for u 1:N_user idx (u-1)*N_sc_per_user (1:N_sc_per_user); freq_grid(idx) user_sym{u}; end % 注意這里沒有做 DC 置零和邊緣保護實際系統(tǒng)要留 guard band % IFFT 變換到時域 time_signal ifft(freq_grid, N_fft) * sqrt(N_fft); % 加循環(huán)前綴 tx_signal [time_signal(end-N_cp1:end); time_signal];這段代碼里最容易出錯的是ifft之后的歸一化系數(shù)。MATLAB 的ifft默認除以 N如果不乘sqrt(N_fft)發(fā)端功率會隨 FFT 點數(shù)變化后面加噪聲時信噪比就對不上了。另一個點是pskmod的相位偏移參數(shù)QPSK 用pi/4可以避免星座點落在坐標軸上但接收端解調(diào)時必須用同樣的偏移否則星座圖會整體旋轉(zhuǎn)。資源映射的索引idx也要仔細檢查連續(xù)分配時第 u 個用戶的起始位置是(u-1)*N_sc_per_user 1如果寫成(u-1)*N_sc_per_user就會從 0 開始MATLAB 會報索引錯誤。2.3 接收端 FFT 與用戶數(shù)據(jù)提取接收端拿到的是經(jīng)過信道和噪聲的時域信號先去 CP再做 FFT 回到頻域然后按發(fā)端相同的分配表取每個用戶的符號。% 假設(shè) rx_signal 是接收到的時域信號長度與 tx_signal 一致 rx_no_cp rx_signal(N_cp1:end); % 去循環(huán)前綴 rx_freq fft(rx_no_cp, N_fft) / sqrt(N_fft); % 歸一化 % 按用戶提取頻域符號 rx_sym cell(1, N_user); for u 1:N_user idx (u-1)*N_sc_per_user (1:N_sc_per_user); rx_sym{u} rx_freq(idx); end % 信道均衡這里假設(shè)已知理想信道實際需要估計 % H_est 是每個子載波的信道響應(yīng)長度 N_fft % rx_freq_eq rx_freq ./ H_est;去 CP 時索引從N_cp1開始是因為發(fā)端把 CP 加在了前面。如果 CP 長度設(shè)得比信道最大時延還短就會引入符號間干擾這時候誤碼率曲線會出現(xiàn)誤碼平臺怎么加發(fā)射功率都降不下去。一般 CP 長度取信道最大時延擴展的 2 到 4 倍室內(nèi)場景常用 16 或 32 個采樣點。3. 信道估計與均衡讓多用戶解調(diào)不再靠“猜”3.1 塊狀導頻和梳狀導頻怎么選OFDMA 仿真里信道估計的導頻圖案直接決定了估計精度和開銷。塊狀導頻是在某些 OFDM 符號的所有子載波上都放導頻適合信道變化慢的場景梳狀導頻是在每個 OFDM 符號的固定間隔子載波上放導頻適合信道變化快的場景。我一般先在時不變信道下用塊狀導頻把鏈路跑通確認資源映射和同步?jīng)]問題再換成梳狀導頻去測時變信道下的跟蹤性能。如果一上來就用梳狀導頻同步稍微偏一點導頻位置的信道估計就會錯后面插值出來的整個信道響應(yīng)都是歪的排查起來非常費勁。3.2 LS 估計與 MMSE 估計的代碼實現(xiàn)最小二乘LS估計最簡單直接用接收導頻除以本地導頻。最小均方誤差MMSE估計需要知道信道統(tǒng)計特性但性能更好。下面給出 LS 估計的代碼MMSE 在此基礎(chǔ)上乘一個由信噪比和信道相關(guān)矩陣決定的濾波矩陣。% 假設(shè)導頻位置索引為 pilot_idx本地導頻符號為 pilot_sym % rx_pilot 是接收到的導頻位置符號 H_ls rx_pilot ./ pilot_sym; % LS 估計 % 對數(shù)據(jù)子載波做插值這里用線性插值 H_est interp1(pilot_idx, H_ls, (1:N_fft)., linear, extrap); % 均衡 rx_freq_eq rx_freq ./ H_est;interp1的最后一個參數(shù)extrap很重要因為邊緣子載波可能沒有導頻覆蓋不 extrap 會返回 NaN后面解調(diào)直接崩掉。LS 估計在低信噪比下噪聲放大明顯如果發(fā)現(xiàn)均衡后星座圖在低 SNR 時反而比高 SNR 還散大概率就是 LS 估計的噪聲被除進去了。這時候要么改用 MMSE要么在 LS 之后加一個簡單的滑動平均濾波。3.3 多用戶之間的干擾怎么在仿真里體現(xiàn)OFDMA 的子載波是正交分配的理論上用戶之間沒有多址干擾。但仿真里如果資源映射的索引有重疊或者 IFFT 點數(shù)與子載波總數(shù)不匹配就會人為引入干擾。我習慣在資源映射之后加一句檢查sum(freq_grid ~ 0)應(yīng)該等于N_user * N_sc_per_user如果不等說明有子載波被重復寫入或者漏寫。另外接收端提取用戶數(shù)據(jù)時如果索引和發(fā)端不一致會把別人的數(shù)據(jù)當成自己的解調(diào)誤碼率直接接近 0.5這時候看星座圖會發(fā)現(xiàn)點均勻散布沒有聚類。4. 避坑與排查OFDMA 仿真里最容易翻車的五個地方4.1 誤碼率曲線在高信噪比下不降反升現(xiàn)象SNR 超過 15 dB 后誤碼率曲線開始往上翹。原因通常是定時同步偏差導致的符號間干擾或者信道估計在低噪聲下把導頻處的噪聲也估計進去了。解決方法是檢查同步算法里的相關(guān)峰位置確保 FFT 窗口沒有偏移同時在 LS 估計后加一個基于 DFT 的降噪把時域信道響應(yīng)截斷到 CP 長度以內(nèi)。4.2 星座圖整體旋轉(zhuǎn)現(xiàn)象接收端解調(diào)前的星座圖是一個旋轉(zhuǎn)過的 QPSK點與點之間角度不對。原因是發(fā)端和收端的相位偏移參數(shù)不一致或者信道估計沒有補償公共相位誤差。解決方法是確認pskmod和pskdemod的相位參數(shù)相同并在均衡后加一個基于導頻的公共相位校正。4.3 資源映射后子載波功率忽大忽小現(xiàn)象不同用戶的發(fā)射功率差異很大導致弱用戶誤碼率很高。原因是 IFFT 前沒有做功率歸一化或者不同用戶的調(diào)制階數(shù)不同但符號功率沒對齊。解決方法是在資源映射前把每個用戶的符號功率歸一化到 1或者在接收端按用戶做自動增益控制。4.4 循環(huán)前綴長度不夠?qū)е抡`碼平臺現(xiàn)象無論怎么提高 SNR誤碼率都卡在 1e-2 左右降不下去。原因是 CP 長度小于信道最大時延符號間干擾無法消除。解決方法是把 CP 長度增加到信道時延擴展的 2 倍以上或者改用頻域均衡加更長的 CP。4.5 多用戶數(shù)據(jù)提取時索引越界現(xiàn)象MATLAB 報“索引超出矩陣維度”。原因是用戶數(shù)乘以每用戶子載波數(shù)不等于 FFT 點數(shù)或者索引計算時用了從 0 開始的編號。解決方法是在資源映射前加斷言assert(N_user * N_sc_per_user N_fft)并統(tǒng)一用從 1 開始的索引。5. 把仿真跑成可復用的驗證平臺參數(shù)化與批量跑 SNR 點5.1 用結(jié)構(gòu)體管理參數(shù)避免腳本里到處是魔法數(shù)字一套 OFDMA 仿真系統(tǒng)要反復調(diào)參數(shù)如果每個數(shù)字都硬編碼在腳本里改一個 FFT 點數(shù)就要翻遍全文。我習慣把所有參數(shù)塞進一個結(jié)構(gòu)體主腳本只負責調(diào)用。cfg.N_fft 64; cfg.N_cp 16; cfg.N_user 4; cfg.mod_order 4; cfg.SNR_dB 0:2:20; cfg.N_frame 1000; % 每個 SNR 點跑 1000 幀 ber zeros(size(cfg.SNR_dB)); for i 1:length(cfg.SNR_dB) err_cnt 0; bit_cnt 0; for f 1:cfg.N_frame [tx, tx_bits] ofdma_tx(cfg); rx awgn(tx, cfg.SNR_dB(i), measured); rx_bits ofdma_rx(rx, cfg); err_cnt err_cnt sum(rx_bits ~ tx_bits); bit_cnt bit_cnt length(tx_bits); end ber(i) err_cnt / bit_cnt; end semilogy(cfg.SNR_dB, ber, o-); grid on; xlabel(SNR (dB)); ylabel(BER);把收發(fā)封裝成ofdma_tx和ofdma_rx兩個函數(shù)主腳本只做循環(huán)和統(tǒng)計。這樣換調(diào)制方式、換用戶數(shù)、換信道模型都只改cfg不用動主邏輯。awgn的measured選項會先測量信號功率再按 SNR 加噪聲比手動算噪聲功率更省事但要注意它測量的是整個幀的功率如果幀內(nèi)有靜默期測出來的功率會偏低。5.2 用理論誤碼率曲線驗證仿真是否正確QPSK 在 AWGN 下的理論誤碼率是ber_theory 0.5 * erfc(sqrt(10^(SNR_dB/10) / 2))。把仿真曲線和理論曲線畫在一起如果差距在 1 dB 以內(nèi)說明鏈路基本正確如果差 3 dB 以上優(yōu)先檢查歸一化系數(shù)和信道估計。我一般會在仿真腳本最后加一段對比代碼省得每次靠肉眼判斷。snr_lin 10.^(cfg.SNR_dB/10); ber_theory 0.5 * erfc(sqrt(snr_lin / 2)); semilogy(cfg.SNR_dB, ber, o-, cfg.SNR_dB, ber_theory, k--); legend(仿真, 理論);5.3 一個容易被忽略的細節(jié)隨機種子跑批量仿真時如果每次randi的種子都不同不同 SNR 點的比特流不一樣誤碼率曲線會有抖動。我習慣在循環(huán)開始前固定隨機種子rng(42)這樣每次跑出來的曲線可復現(xiàn)調(diào)參數(shù)時也能排除隨機性的干擾。等鏈路驗證通過后再換幾個不同的種子跑幾遍確認曲線穩(wěn)定。這套仿真系統(tǒng)本身不復雜難的是每個環(huán)節(jié)的歸一化、索引和同步細節(jié)。我自己的習慣是每加一個模塊就先在理想信道下跑通再加噪聲再加信道最后加多用戶。一次只改一個變量出了問題才能定位到具體哪一行。希望幫到你。本文還有配套的精品資源點擊獲取