天天看點

了解OFDM技術原理一、為什麼要用OFDM?二、如何實作OFDM?三、 OFDM的工程化實作四、基于FFT的OFDM實作Matlab代碼參考文章:

一、為什麼要用OFDM?

  通信有一個長期的使用者需求——快,但是又受到可用電磁頻譜的實體限制——少,因而如何在較少可用的頻譜上更快的傳輸資訊一直是通信技術發展的核心驅動力。

  常用的單載波傳輸(如ASK,FSK,和PSK等)在一個符号傳輸中使用單個載波承載資訊,因而速率較低。想要提高傳輸速度,需要縮短碼元持續時間,對頻帶寬度需求增加,對均衡器和均衡算法要求提高,實作困難。

因而很自然地考慮将頻帶進行劃分,實作一段時域信号中包含多個頻段的信号分量(如下圖),不同頻段的信号承載不同的資訊,可以實作多路信号的并行傳輸,常用的技術有FDM和OFDM。

了解OFDM技術原理一、為什麼要用OFDM?二、如何實作OFDM?三、 OFDM的工程化實作四、基于FFT的OFDM實作Matlab代碼參考文章:

  FDM将頻譜分割成多塊,并在多塊之間設定保護帶寬。這是因為,保護帶寬可以避免不同頻段之間互相幹擾,且便于使用濾波器的調制解調(濾波器一般為非理想,也即濾波器邊沿不會很陡峭),但這樣會造成頻帶使用率下降。

了解OFDM技術原理一、為什麼要用OFDM?二、如何實作OFDM?三、 OFDM的工程化實作四、基于FFT的OFDM實作Matlab代碼參考文章:

  OFDM技術就正面直視并解決了這種載波互相幹擾的問題,是 一種頻帶使用更加緊湊的特殊FDM ,如下圖。OFDM的解決方法就是引入了正交的特性,這裡正交的意思是,下圖中頻域采樣點(紅色箭頭)隻與目前載波頻率有關,不受其他載波影響。這樣就可以在有較高頻帶使用率的情況下并行發送更多資訊,也可以說:OFDM技術可以在較少帶寬中更快的發送資訊。

了解OFDM技術原理一、為什麼要用OFDM?二、如何實作OFDM?三、 OFDM的工程化實作四、基于FFT的OFDM實作Matlab代碼參考文章:

二、如何實作OFDM?

  如何才能讓信号具有上圖所示的OFDM頻帶正交的特點呢?請先看下面兩個信号的構造過程:

了解OFDM技術原理一、為什麼要用OFDM?二、如何實作OFDM?三、 OFDM的工程化實作四、基于FFT的OFDM實作Matlab代碼參考文章:
了解OFDM技術原理一、為什麼要用OFDM?二、如何實作OFDM?三、 OFDM的工程化實作四、基于FFT的OFDM實作Matlab代碼參考文章:

  上面兩個圖都表示,無限長周期正弦信号與同等長度的矩形門函數相乘得到我們常看到的被截斷的有限長載波信号,于此同時頻域完成頻譜的卷積過程得到中心頻點不同的形狀相同的sinc函數。如果将上圖兩個實傳信号相加,其頻譜可如下圖所示:

了解OFDM技術原理一、為什麼要用OFDM?二、如何實作OFDM?三、 OFDM的工程化實作四、基于FFT的OFDM實作Matlab代碼參考文章:

  先不用關心他們的橫坐标對應的數值,上面這個實驗給我們啟發:選取一個長度為 T s {{T}_{s}} Ts​的矩形窗函數乘以不同頻率的正弦信号(或餘弦信号)就可得到OFDM波形。通過傅裡葉變換的知識,我們可以知道,矩形窗函數的長度 T s {{T}_{s}} Ts​決定着sinc函數的中心頻點距離第一零點的距離 Δ f \Delta f Δf,如下圖所示(左側時域,右側頻域):

了解OFDM技術原理一、為什麼要用OFDM?二、如何實作OFDM?三、 OFDM的工程化實作四、基于FFT的OFDM實作Matlab代碼參考文章:
了解OFDM技術原理一、為什麼要用OFDM?二、如何實作OFDM?三、 OFDM的工程化實作四、基于FFT的OFDM實作Matlab代碼參考文章:

  總的來說,要想實作OFDM,隻需要滿足各相鄰子載波的頻率最小間隔滿足 Δ f = 1 T s \Delta f=\frac{1}{{{T}_{s}}} Δf=Ts​1​。

注意:

  • 這裡隻是在講多載波頻率如何設定,沒有說幅度和相位的問題,因而可以引入PSK和QAM技術對ODFM進一步加速!
  • 上述為基帶信号,實際場景在發送前還需要把基帶信号調制到相應的頻段上才能發射出去。

三、 OFDM的工程化實作

方法一:

基帶ODFM信号表達式: z ( t ) = ∑ k = 0 N − 1 B k cos ⁡ ( 2 π f k t ) z\left( t \right)=\sum\limits_{k=0}^{N-1}{{{B}_{k}}\cos \left( 2\pi {{f}_{k}}t \right)} z(t)=k=0∑N−1​Bk​cos(2πfk​t)

為了可承載很多的資訊比特, z ( t ) = ∑ k = 0 N − 1 B k e 2 π f k t z\left( t \right)=\sum\limits_{k=0}^{N-1}{{{\mathbf{B}}_{k}}{{e}^{2\pi {{f}_{k}}t}}} z(t)=k=0∑N−1​Bk​e2πfk​t,其中 B k = a k + j b k {{\mathbf{B}}_{k}}={{a}_{k}}+j{{b}_{k}} Bk​=ak​+jbk​

展開後可以得到:

Re ⁡ { z ( t ) } = ∑ k = 0 N − 1 a k cos ⁡ ( 2 π f k t ) − b k s i n ( 2 π f k t ) \operatorname{Re}\left\{ z\left( t \right) \right\}=\sum\limits_{k=0}^{N-1}{{{a}_{k}}\cos \left( 2\pi {{f}_{k}}t \right)-{{b}_{k}}sin\left( 2\pi {{f}_{k}}t \right)} Re{z(t)}=k=0∑N−1​ak​cos(2πfk​t)−bk​sin(2πfk​t)

Im ⁡ { z ( t ) } = ∑ k = 0 N − 1 a k s i n ( 2 π f k t ) + b k cos ⁡ ( 2 π f k t ) \operatorname{Im}\left\{ z\left( t \right) \right\}=\sum\limits_{k=0}^{N-1}{{{a}_{k}}sin\left( 2\pi {{f}_{k}}t \right)+{{b}_{k}}\cos \left( 2\pi {{f}_{k}}t \right)} Im{z(t)}=k=0∑N−1​ak​sin(2πfk​t)+bk​cos(2πfk​t)

OFDM射頻信号:

了解OFDM技術原理一、為什麼要用OFDM?二、如何實作OFDM?三、 OFDM的工程化實作四、基于FFT的OFDM實作Matlab代碼參考文章:

其中, s k ( t ) = a k cos ⁡ 2 π ( f k + f c ) t − b k sin ⁡ 2 π ( f k + f c ) t {{s}_{k}}\left( t \right)={{a}_{k}}\cos 2\pi \left( {{f}_{k}}+{{f}_{c}} \right)t-{{b}_{k}}\sin 2\pi \left( {{f}_{k}}+{{f}_{c}} \right)t sk​(t)=ak​cos2π(fk​+fc​)t−bk​sin2π(fk​+fc​)t

依賴上面推導,可以使用的調制解調架構如下:

了解OFDM技術原理一、為什麼要用OFDM?二、如何實作OFDM?三、 OFDM的工程化實作四、基于FFT的OFDM實作Matlab代碼參考文章:
了解OFDM技術原理一、為什麼要用OFDM?二、如何實作OFDM?三、 OFDM的工程化實作四、基于FFT的OFDM實作Matlab代碼參考文章:

  通過以上這種方法來實作OFDM信号并發射出去需要耗費很多的混頻器,且要求正弦振蕩器和餘弦振蕩器嚴格正交,工程實作起來很困難,因而出現了下面基于DFT和IDFT的方法。

方法二:

  OFDM基帶信号 z ( t ) = ∑ k = 0 N − 1 B k e 2 π f k t z\left( t \right)=\sum\limits_{k=0}^{N-1}{{{\mathbf{B}}_{k}}{{e}^{2\pi {{f}_{k}}t}}} z(t)=k=0∑N−1​Bk​e2πfk​t和IDFT變換 x [ n ] = 1 N ∑ k = 0 N − 1 X [ k ] e j 2 π k N n x\left[ n \right]=\frac{1}{N}\sum\limits_{k=0}^{N-1}{X\left[ k \right]{{e}^{j2\pi \frac{k}{N}n}}} x[n]=N1​k=0∑N−1​X[k]ej2πNk​n在計算結構上很相似,且計算系數都為離散複數 B k {{\mathbf{B}}_{k}} Bk​和 X [ k ] X\left[ k \right] X[k](這裡不考慮固定系數 1 N \frac{1}{N} N1​的影響,但不同點在于OFDM基帶表達式的輸出為連續信号,而IDFT計算得到離散信号,因而他們之間相差一個D/A變換器,也即對IDFT輸出信号進行D/A處理即可得到OFDM基帶信号輸出。

  另外, 需要為實數(不包含虛部),因而需要根據DFT變換的性質,構造對稱共轭的 B ′ k = [ B k , c o n j ( B k ) ] {{\mathbf{{B}'}}_{k}}=\left[ {{\mathbf{B}}_{k}},conj\left( {{\mathbf{B}}_{k}} \right) \right] B′k​=[Bk​,conj(Bk​)]作為IDFT的輸入。

  IFFT和FFT是IDFT和DFT的快速實作方法,常用的有基2的FFT和基4的FFT,其通過蝶形運算大大簡化了DFT的計算量。

  基于FFT的OFDM調制解調方法如下:

了解OFDM技術原理一、為什麼要用OFDM?二、如何實作OFDM?三、 OFDM的工程化實作四、基于FFT的OFDM實作Matlab代碼參考文章:
了解OFDM技術原理一、為什麼要用OFDM?二、如何實作OFDM?三、 OFDM的工程化實作四、基于FFT的OFDM實作Matlab代碼參考文章:
  • 輔助技術一:循環字首和循環字尾
    了解OFDM技術原理一、為什麼要用OFDM?二、如何實作OFDM?三、 OFDM的工程化實作四、基于FFT的OFDM實作Matlab代碼參考文章:

      為了消除碼間串擾(ISI)需要在兩個OFDM符号之間引入保護間隔GI,如果采用全零的簡單保護間隔會引起載波間幹擾(ICI),最好的方法是需要添加循環字首(CP),其長度約為OFDM符号長度的1/4~1/6。

      通常我們隻關注因相鄰OFDM符号滞後引起的ISI而添加CP,在一些場景中也會有超前的情況,同樣的道理需要添加循環字尾(CS)。

    了解OFDM技術原理一、為什麼要用OFDM?二、如何實作OFDM?三、 OFDM的工程化實作四、基于FFT的OFDM實作Matlab代碼參考文章:
      相比于使用者0,使用者1和使用者2分别有滞後和超前的情況,因而需要CP和CS同時使用,添加方法如下所示。
    了解OFDM技術原理一、為什麼要用OFDM?二、如何實作OFDM?三、 OFDM的工程化實作四、基于FFT的OFDM實作Matlab代碼參考文章:
  • 輔助技術二:餘弦滾降窗

      一個OFDM符号内的多個載波時域疊加,對應地,頻域中多個sinc函數也會疊加,效果如下圖所示。可見其頻譜并不是個理想矩形,而是有明顯的帶外輻射現象。為了減弱這種帶外輻射,需要對時域多載波疊加信号進行加窗處理,常用的加窗方法是加餘弦窗函數。

    了解OFDM技術原理一、為什麼要用OFDM?二、如何實作OFDM?三、 OFDM的工程化實作四、基于FFT的OFDM實作Matlab代碼參考文章:
    了解OFDM技術原理一、為什麼要用OFDM?二、如何實作OFDM?三、 OFDM的工程化實作四、基于FFT的OFDM實作Matlab代碼參考文章:
    了解OFDM技術原理一、為什麼要用OFDM?二、如何實作OFDM?三、 OFDM的工程化實作四、基于FFT的OFDM實作Matlab代碼參考文章:

四、基于FFT的OFDM實作Matlab代碼

clear all;
close all;
carrier_count=200;
%這個程式中OFDM子載波個數為512,其中400即carrier_count*2為資料符号,其餘賦0值。
symbols_per_carrier=20;%每個子載波上的符号數,在這裡即為OFDM符号的個數。
bits_per_symbol=4;%OFDM符号的每個子載波上傳輸的比特數。4比特通常采用16QAM調制。
IFFT_bin_length=512;%FFT長度,也即一個OFDM符号的子載波的個數。
PrefixRatio=1/4;%循環字首的比率,即循環字首與OFDM符号長度的比值,通常在 1/6~1/4之間。
GI=PrefixRatio*IFFT_bin_length ;%保護間隔的長度,這裡為128。
beta=1/32;%升餘弦窗的滾降系數。
GIP=beta*(IFFT_bin_length+GI);%循環字尾的長度,這裡為20
SNR=30; %本程式考慮加性高斯白噪聲信道,這裡信噪比為30dB。

%===============================OFDM信号産生=============================
baseband_out_length = carrier_count * symbols_per_carrier * bits_per_symbol;
%計算傳輸資料總的比特數,為200*20*4=16000比特。16000比特的構成為20個OFDM符号,每個OFDM
%符号200個子載波,每個子載波傳輸4比特資訊。
carriers=(1:carrier_count)+(floor(IFFT_bin_length/4) - floor(carrier_count/2));
%計算OFDM符号子載波的序号,carriers中存放的序号是29~228。 
conjugate_carriers = IFFT_bin_length - carriers + 2;
%計算OFDM符号子載波的序号,conjugate_carriers中存放的序号是282~481。
rand( 'twister',0)
baseband_out=round(rand(1,baseband_out_length));
%産生16000比特待傳輸的二進制比特流。這裡存放的是發送的二進制信号與後面解調後的二進制信号比
%較,可以計算誤碼率。

%16QAM調制并繪制星座圖
complex_carrier_matrix=qam16(baseband_out);
%調用子程式qam16進行16QAM調制。将baseband_out中的二進制比特流,每4比特轉換為一個16QAM信
%号,即将二進制比特流每4比特轉換為-3-3j、-3+3j、3-3j、3+3j、-1-3j、-1+3j、1-3j、1+3j、%-3-j、-3+j、3-j、3+j、-1-j、-1+j、1-j、1+j中的一個。轉換後complex_carrier_matrix為%1*4000矩陣。
complex_carrier_matrix=reshape(complex_carrier_matrix',carrier_count,symbols_per_carrier)';
%轉換complex_carrier_matrix中的資料為carrier_count*symbols_per_carrier矩陣,這裡為%20*200矩陣。
figure(1);
plot(complex_carrier_matrix,'*r');% 繪制16QAM星座圖
axis([-4, 4, -4, 4]);
title('16QAM調制後星座圖');
grid on

%IFFT,即進行OFDM調制。
IFFT_modulation=zeros(symbols_per_carrier,IFFT_bin_length);
%将symbols_per_carrier*IFFT_bin_length矩陣賦0值,這裡将20*512矩陣賦0值。這裡512是%IFFT的長度,也是OFDM符号子載波的個數。
 IFFT_modulation(:,carriers ) = complex_carrier_matrix ;
%将20*200的complex_carrier_matrix的資料賦給IFFT_modulation的第29~228列,即給512個子%載波中的29~229個子載波指派。
IFFT_modulation(:,conjugate_carriers ) = conj(complex_carrier_matrix);
%将20*200的complex_carrier_matrix的資料賦給512個子載波中的第282~481個子載波。
%這段程式構造了512個子載波的OFDM符号,并且各個子載波上的資料是共轭對稱的。這樣做的目的是經過%IFFT後形成的OFDM符号均為實數。另外,在512個子載波中,僅有400個子載波為資料,其餘為0值。相%當于補零,補零的目的是通常IFFT的長度應該為2的整數次幂。
signal_after_IFFT=ifft(IFFT_modulation,IFFT_bin_length,2);%IFFT實作OFDM調制。
time_wave_matrix=signal_after_IFFT;%
figure(2);
plot(0:IFFT_bin_length-1,time_wave_matrix(2,:));%畫一個OFDM信号的時域表現
axis([0, 512, -0.4, 0.4]);
grid on;
ylabel('Amplitude');
xlabel('Time');
title('OFDM Time Signal, One Symbol Period');


%添加循環字首與循環字尾
XX=zeros(symbols_per_carrier,IFFT_bin_length+GI+GIP);
%IFFT_bin_length+GI+GIP為OFDM、循環字首、循環字尾長度之和。
for k=1:symbols_per_carrier
        for i=1:IFFT_bin_length
            XX(k,i+GI)=signal_after_IFFT(k,i);
        end
        for i=1:GI
            XX(k,i)=signal_after_IFFT(k,i+IFFT_bin_length-GI);%添加循環字首
        end
        for j=1:GIP
            XX(k,IFFT_bin_length+GI+j)=signal_after_IFFT(k,j);%添加循環字尾
        end
end
time_wave_matrix_cp=XX;%帶循環字首與循環字尾的OFDM符号。
figure(3);
plot(0:length(time_wave_matrix_cp)-1,time_wave_matrix_cp(2,:));
%畫帶循環字首與循環字尾的OFDM信号的時域波形 
axis([0, 600, -0.3, 0.3]);
grid on;
ylabel('Amplitude');
xlabel('Time');
title('OFDM Time Signal with CP, One Symbol Period');
%OFDM符号加窗
windowed_time_wave_matrix_cp=zeros(1,IFFT_bin_length+GI+GIP);
for i = 1:symbols_per_carrier 
windowed_time_wave_matrix_cp(i,:) =real(time_wave_matrix_cp(i,:)).*rcoswindow(beta,IFFT_bin_length+GI)';
%調用rcoswindow産生升餘弦窗,對帶循環字首與循環字尾的OFDM符号加窗。
end  
figure(4);
plot(0:IFFT_bin_length-1+GI+GIP,windowed_time_wave_matrix_cp(2,:));
%畫加窗後的OFDM符号
axis([0, 700, -0.2, 0.2]);
grid on;
ylabel('Amplitude');
xlabel('Time');
title('OFDM Time Signal Apply a Window , One Symbol Period');

%生成發送信号,并串變換
windowed_Tx_data=zeros(1,symbols_per_carrier*(IFFT_bin_length+GI)+GIP);
%注意并串變換後資料的長度為symbols_per_carrier*(IFFT_bin_length+GI)+GIP,這裡考慮了循環字首與循環字尾的重疊相加。
windowed_Tx_data(1:IFFT_bin_length+GI+GIP)=windowed_time_wave_matrix_cp(1,:);
%賦第一個加窗帶循環字首字尾的OFDM符号至windowed_Tx_data,即發送串行資料。
for i = 1:symbols_per_carrier-1 
windowed_Tx_data((IFFT_bin_length+GI)*i+1:(IFFT_bin_length+GI)*(i+1)+GIP)=windowed_time_wave_matrix_cp(i+1,:);%并串變換,循環字首與循環字尾重疊相加
end
Tx_data_withoutwindow=reshape(time_wave_matrix_cp',(symbols_per_carrier)*(IFFT_bin_length+GI+GIP),1)';
%不加窗資料并串變換
Tx_data=reshape(windowed_time_wave_matrix_cp',(symbols_per_carrier)*(IFFT_bin_length+GI+GIP),1)';
%加窗資料,但按照循環字首與循環字尾不重疊相加進行并串變換。此時資料長度為%(symbols_per_carrier)*(IFFT_bin_length+GI+GIP)。
temp_time1 = (symbols_per_carrier)*(IFFT_bin_length+GI+GIP);
%加窗,循環字首與循環字尾不重疊資料長度,即為發送的總的資料比特數
figure (5)
subplot(2,1,1);
plot(0:temp_time1-1,Tx_data );%畫循環字首與循環字尾不重疊相加OFDM信号的時域波形
grid on
ylabel('Amplitude (volts)')
xlabel('Time (samples)')
title('OFDM Time Signal')
temp_time2 =symbols_per_carrier*(IFFT_bin_length+GI)+GIP;
%加窗,循環字首與循環字尾重疊相加資料長度
subplot(2,1,2);
plot(0:temp_time2-1,windowed_Tx_data);
%畫循環字首與循環字尾重疊相加OFDM信号的時域波形
grid on
ylabel('Amplitude (volts)')
xlabel('Time (samples)')
title('OFDM Time Signal')

%====================經過加性高斯白噪聲信道=======----================== 
Tx_signal_power = var(windowed_Tx_data);% 計算信号功率
linear_SNR=10^(SNR/10);% 轉換對數信噪比為線性幅度值
noise_sigma=Tx_signal_power/linear_SNR;%計算噪聲功率,也就是方差
noise_scale_factor = sqrt(noise_sigma);% 計算标準差
noise=randn(1,((symbols_per_carrier)*(IFFT_bin_length+GI))+GIP)*noise_scale_factor;% 産生功率為noise_scale_factor高斯噪聲
Rx_data=windowed_Tx_data +noise;
%在發送資料上加噪聲,相當于OFDM信号經過加性高斯白噪聲信道。

%==========================OFDM信号解調=============================== 
Rx_data_matrix=zeros(symbols_per_carrier,IFFT_bin_length+GI+GIP);
%存放并行的接收資料
for i=1:symbols_per_carrier
    Rx_data_matrix(i,:)=Rx_data(1,(i-1)*(IFFT_bin_length+GI)+1:i*(IFFT_bin_length+GI)+GIP);% 串并變換
end
Rx_data_complex_matrix=Rx_data_matrix(:,GI+1:GI+IFFT_bin_length);
% 去掉循環字首與循環字尾,取出OFDM符号傳輸的資料
Y1=fft(Rx_data_complex_matrix,IFFT_bin_length,2);% 求FFT,即OFDM信号解調
Rx_carriers=Y1(:,carriers);
% 取出carriers序号對應的子載波上的發送資料,去掉加入的零及共轭對稱部分
Rx_phase =angle(Rx_carriers);% 計算接收信号的相位特性
Rx_mag = abs(Rx_carriers);% 計算接收信号的幅度特性
[M, N]=pol2cart(Rx_phase, Rx_mag);%轉換極坐标資料為直角坐标資料 
Rx_complex_carrier_matrix = complex(M, N);%兩個直角坐标的實資料為構成複資料。
figure(7);
plot(Rx_complex_carrier_matrix,'*r');%畫接收信号的星座圖 
axis([-4, 4, -4, 4]);
title('SNR=30dB接收資料星座圖');
grid on
           
function [complex_qam_data]=qam16(bitdata)
%輸入參數:bitdata為二進制數位流
%輸出參數:complex_qam_data為16QAM複信号
X1=reshape(bitdata,4,length(bitdata)/4)';%将二進制數位流以4比特分段
d=1;
%轉換4比特二進制碼為十進制碼1~16,生成mapping映射表中的索引。
for i=1:length(bitdata)/4;
    for j=1:4
        X1(i,j)=X1(i,j)*(2^(4-j));
    end
    source(i,1)=1+sum(X1(i,:));
end
%16QAM映射表,該表中存放的是16對,每對兩個實數,辨別星座位置。
mapping=[-3*d 3*d; -d  3*d;d  3*d;3*d  3*d;-3*d  d; -d  d; d  d;3*d  d; -3*d  -d; -d  -d; d  -d;3*d  -d;-3*d  -3*d;-d  -3*d; d  -3*d;3*d  -3*d];
for i=1:length(bitdata)/4
    qam_data(i,:)=mapping(source(i),:);%資料映射
end
complex_qam_data=complex(qam_data(:,1),qam_data(:,2));
%組合為複數形式,形成16QAM信号。
end
           
function [rcosw]=rcoswindow(beta, Ts) 
%輸入參數:beta為升餘弦窗滾降系數,Ts為IFFT長度加循環字首長度
t=0:(1+beta)*Ts;
rcosw=zeros(1,(1+beta)*Ts);
%計算升餘弦窗,共有三部分
for i=1:beta*Ts;
rcosw(i)=0.5+0.5*cos(pi+ t(i)*pi/(beta*Ts));%計算升餘弦窗第一部分
end
rcosw(beta*Ts+1:Ts)=1;%升餘弦窗第二部分
for j=Ts+1:(1+beta)*Ts+1;
        rcosw(j-1)=0.5+0.5*cos((t(j)-Ts)*pi/(beta*Ts));%計算升餘弦窗第三部分
end
rcosw=rcosw';% 轉換為列向量
end
           

參考文章:

  1. 《深入淺出通信原理》,陳愛軍
  2. 《MIMO-OFDM系統原理、應用及仿真》,李莉
  3. 《通信原理》,樊昌信
  4. https://www.bilibili.com/video/BV12z411B7y9?from=search&seid=3227339177102192107
  5. https://blog.csdn.net/qq_16923717/article/details/83623571
  6. https://blog.csdn.net/rs_network/article/details/50682369
  7. https://blog.csdn.net/a493823882/article/details/80058002

繼續閱讀