4G/LTE - PHY Channel

 

 

 

 

PSS (Primary Synchronization Channel)

 

PSS is a specific physical layer signal that is used for radio frame synchronization. It has characterstics as listed below.

  • Mapped to 72 active sub carriers(6 resource blocks), centered around the DC subcarrier in slot 0 (Subframe 0) and slot 10 (Subframe 5) in FDD.
  • Mapped to 72 active sub carriers(6 resource blocks), centered around the DC subcarrier in slot 2 (Subframe 2) and slot 12 (Subframe 6) in TDD.
  • Made up of 62 Zadoff Chu Sequence Values
  • Used for Downlink Frame Synchronization
  • One of the critical factors determining Physical Cell ID

This may not be an important topic for most of the case since it would be working fine for most of the device that you have for test. Otherwise it would have not been given to you for test.

However, If you are a developer working at early stage of LTE chipset, this would be one of the first signal you have to implement.

 

 

 

Algorithm for PSS Generation

 

The exact PSS symbol calculation is done by the following formula as described in 36.211 - 6.11.1. For the specific example of generated PSS, refer to Matlab : Toolbox : LTE : PSS page or PSS with default Matlab function.

 

 

 

 

Generating PSS with default Matlab function

 

Followings are the Matlab code for generating PSS and its result for each NID value.

    clear all;

     

    u_shift = [25 29 34];

     

    NID = 0;

     

    d_u = [];

     

    for n = 0:61

        

        u = u_shift(NID+1);

        

        if n <= 30

            d = exp(-j*pi*u*n*(n+1)/63);    

        else

             d = exp(-j*pi*u*(n+1)*(n+2)/63);   

        end;

        

        d_u = [d_u d];

     

    end;

     

    subplot(1,3,1);

    plot(real(d_u(1:31)),imag(d_u(1:31)),'ko','MarkerFaceColor',[0 0 0]);

    axis([-1.5 1.5 -1.5 1.5]);

    title('n=0..30');

     

    subplot(1,3,2);

    plot(real(d_u(32:62)),imag(d_u(32:62)),'bo','MarkerFaceColor',[0 0 1]);

    axis([-1.5 1.5 -1.5 1.5]);

    title('n=31..61');

     

    subplot(1,3,3);

    plot(real(d_u(1:62)),imag(d_u(1:62)),'ro','MarkerFaceColor',[1 0 0]);

    axis([-1.5 1.5 -1.5 1.5]);

    title('n=0..61');

     

NID

Result

0

1

2

 

Followings are the numberical result (print out of d_u[ ] array) for each NID;

NID = 0

NID = 1

NID = 2

   1.0000 + 0.0000i

  -0.7971 - 0.6038i

   0.3653 - 0.9309i

  -0.7331 - 0.6802i

   0.9802 + 0.1981i

   0.9556 + 0.2948i

  -0.5000 - 0.8660i

   0.7660 - 0.6428i

  -0.2225 - 0.9749i

   0.6235 + 0.7818i

   0.4562 + 0.8899i

   0.3653 - 0.9309i

   0.9556 + 0.2948i

   0.7660 - 0.6428i

  -0.5000 + 0.8660i

  -0.7331 + 0.6802i

   0.9802 + 0.1981i

  -0.2225 + 0.9749i

   0.6235 + 0.7818i

  -0.7971 - 0.6038i

  -0.5000 - 0.8660i

  -0.5000 + 0.8660i

  -0.7971 - 0.6038i

  -0.9888 + 0.1490i

   0.9556 - 0.2948i

   0.9802 + 0.1981i

  -0.2225 - 0.9749i

   1.0000 - 0.0000i

   0.7660 - 0.6428i

  -0.7331 + 0.6802i

  -0.9888 + 0.1490i

  -0.9888 + 0.1490i

  -0.7331 + 0.6802i

   0.7660 - 0.6428i

   1.0000 - 0.0000i

  -0.2225 - 0.9749i

   0.9802 + 0.1981i

   0.9556 - 0.2948i

  -0.9888 + 0.1490i

  -0.7971 - 0.6038i

  -0.5000 + 0.8660i

  -0.5000 - 0.8660i

  -0.7971 - 0.6038i

   0.6235 + 0.7818i

  -0.2225 + 0.9749i

   0.9802 + 0.1981i

  -0.7331 + 0.6802i

  -0.5000 + 0.8660i

   0.7660 - 0.6428i

   0.9556 + 0.2948i

   0.3653 - 0.9309i

   0.4562 + 0.8899i

   0.6235 + 0.7818i

  -0.2225 - 0.9749i

   0.7660 - 0.6428i

  -0.5000 - 0.8660i

   0.9556 + 0.2948i

   0.9802 + 0.1981i

  -0.7331 - 0.6802i

   0.3653 - 0.9309i

  -0.7971 - 0.6038i

   1.0000 + 0.0000i

   1.0000 + 0.0000i

  -0.9691 - 0.2468i

  -0.7331 - 0.6802i

   0.0747 + 0.9972i

  -0.7971 + 0.6038i

   0.8262 + 0.5633i

  -0.5000 + 0.8660i

   0.7660 + 0.6428i

  -0.9010 + 0.4339i

  -0.2225 + 0.9749i

  -0.4113 - 0.9115i

  -0.7331 - 0.6802i

   0.8262 + 0.5633i

   0.7660 + 0.6428i

  -0.5000 - 0.8660i

   0.0747 - 0.9972i

  -0.7971 + 0.6038i

  -0.9010 - 0.4339i

  -0.2225 + 0.9749i

  -0.9691 - 0.2468i

  -0.5000 + 0.8660i

  -0.5000 - 0.8660i

  -0.9691 - 0.2468i

   0.9556 - 0.2948i

   0.8262 - 0.5633i

  -0.7971 + 0.6038i

  -0.9010 + 0.4339i

   1.0000 - 0.0000i

   0.7660 + 0.6428i

   0.0747 - 0.9972i

   0.9556 - 0.2948i

   0.9556 - 0.2948i

   0.0747 - 0.9972i

   0.7660 + 0.6428i

   1.0000 + 0.0000i

  -0.9010 + 0.4339i

  -0.7971 + 0.6038i

   0.8262 - 0.5633i

   0.9556 - 0.2948i

  -0.9691 - 0.2468i

  -0.5000 - 0.8660i

  -0.5000 + 0.8660i

  -0.9691 - 0.2468i

  -0.2225 + 0.9749i

  -0.9010 - 0.4339i

  -0.7971 + 0.6038i

   0.0747 - 0.9972i

  -0.5000 - 0.8660i

   0.7660 + 0.6428i

   0.8262 + 0.5633i

  -0.7331 - 0.6802i

  -0.4113 - 0.9115i

  -0.2225 + 0.9749i

  -0.9010 + 0.4339i

   0.7660 + 0.6428i

  -0.5000 + 0.8660i

   0.8262 + 0.5633i

  -0.7971 + 0.6038i

   0.0747 + 0.9972i

  -0.7331 - 0.6802i

  -0.9691 - 0.2468i

   1.0000 + 0.0000i

   1.0000 + 0.0000i

  -0.9691 + 0.2468i

  -0.7331 + 0.6802i

   0.0747 - 0.9972i

  -0.7971 - 0.6038i

   0.8262 - 0.5633i

  -0.5000 - 0.8660i

   0.7660 - 0.6428i

  -0.9010 - 0.4339i

  -0.2225 - 0.9749i

  -0.4113 + 0.9115i

  -0.7331 + 0.6802i

   0.8262 - 0.5633i

   0.7660 - 0.6428i

  -0.5000 + 0.8660i

   0.0747 + 0.9972i

  -0.7971 - 0.6038i

  -0.9010 + 0.4339i

  -0.2225 - 0.9749i

  -0.9691 + 0.2468i

  -0.5000 - 0.8660i

  -0.5000 + 0.8660i

  -0.9691 + 0.2468i

   0.9556 + 0.2948i

   0.8262 + 0.5633i

  -0.7971 - 0.6038i

  -0.9010 - 0.4339i

   1.0000 + 0.0000i

   0.7660 - 0.6428i

   0.0747 + 0.9972i

   0.9556 + 0.2948i

   0.9556 + 0.2948i

   0.0747 + 0.9972i

   0.7660 - 0.6428i

   1.0000 + 0.0000i

  -0.9010 - 0.4339i

  -0.7971 - 0.6038i

   0.8262 + 0.5633i

   0.9556 + 0.2948i

  -0.9691 + 0.2468i

  -0.5000 + 0.8660i

  -0.5000 - 0.8660i

  -0.9691 + 0.2468i

  -0.2225 - 0.9749i

  -0.9010 + 0.4339i

  -0.7971 - 0.6038i

   0.0747 + 0.9972i

  -0.5000 + 0.8660i

   0.7660 - 0.6428i

   0.8262 - 0.5633i

  -0.7331 + 0.6802i

  -0.4113 + 0.9115i

  -0.2225 - 0.9749i

  -0.9010 - 0.4339i

   0.7660 - 0.6428i

  -0.5000 - 0.8660i

   0.8262 - 0.5633i

  -0.7971 - 0.6038i

   0.0747 - 0.9972i

  -0.7331 + 0.6802i

  -0.9691 + 0.2468i

   1.0000 - 0.0000i

 

 

 

Cross Correlation between different PSS

 

Threre are three types of PSSs in LTE. To make all these three type unique (distinctive to each other), it is designed that the cross correlation between each PSS be very low. Following example shows the cross correlation between each PSS.

 

 

Following is the Matlab source code that produce the result as shown above. I used Matlab dsp package to calculate the cross correlation. If you don't have Matlab dsp package, try to write your own script for calculating cross correlation

    clear all;

     

    u_shift = [25 29 34];

     

    % Generate PSS for NID = 0

     

    NID = 0;

    d_u = [];

     

    for n = 0:61

        

        u = u_shift(NID+1);

        

        if n <= 30

            d = exp(-j*pi*u*n*(n+1)/63);

        else

            d = exp(-j*pi*u*(n+1)*(n+2)/63);

        end;

        

        d_u = [d_u d];

     

    end;

     

    d_u_NID0 = d_u';

     

     

    % Generate PSS for NID = 1

     

    NID = 1;

    d_u = [];

     

    for n = 0:61

        

        u = u_shift(NID+1);

        

        if n <= 30

            d = exp(-j*pi*u*n*(n+1)/63);

        else

            d = exp(-j*pi*u*(n+1)*(n+2)/63);

        end;

        

        d_u = [d_u d];

     

    end;

     

    d_u_NID1 = d_u';

     

     

    % Generate PSS for NID = 2

     

    NID = 2;

    d_u = [];

     

    for n = 0:61

        

        u = u_shift(NID+1);

        

        if n <= 30

            d = exp(-j*pi*u*n*(n+1)/63);

        else

            d = exp(-j*pi*u*(n+1)*(n+2)/63);

        end;

        

        d_u = [d_u d];

     

    end;

     

    d_u_NID2 = d_u';

     

     

    % Cross Correlation between PSS

     

    Hxcorr = dsp.Crosscorrelator;

    taps = 0:61;

    taps = taps';

     

    XCorr_0_0 = step(Hxcorr,d_u_NID0,d_u_NID0);

    XCorr_0_1 = step(Hxcorr,d_u_NID0,d_u_NID1);

    XCorr_0_2 = step(Hxcorr,d_u_NID0,d_u_NID2);

     

    subplot(3,1,1);

    stem(taps,abs(XCorr_0_0(62:end)),'bo','MarkerFaceColor',[0 0 1]);

    xlim([0 length(taps)]); ylim([0 100]);

    title('Corr between PSS(NID0) and PSS(NID0)');

     

    subplot(3,1,2);

    stem(taps,abs(XCorr_0_1(62:end)),'bo','MarkerFaceColor',[0 0 1]);

    xlim([0 length(taps)]); ylim([0 100]);

    title('Corr between PSS(NID0) and PSS(NID1)');

     

    subplot(3,1,3);

    stem(taps,abs(XCorr_0_2(62:end)),'bo','MarkerFaceColor',[0 0 1]);

    xlim([0 length(taps)]); ylim([0 100]);

    title('Corr between PSS(NID0) and PSS(NID2)');

     

     

 

 

 

Correlation between a PSS and its PhaseShifted Version

 

This example shows the correlation between a PSS and its phaseshifted copy. As you see here, the magnitue (absolute value) of correlation does not changes even if you do phase shift. However, you can figure out the degree of phase shift by taking the angle of correlation.

With this property, you can identify a PSS in a received signal without worrying about any possible phase shift that might have occurred by communication channel. In addition, you can figure out the amount of phase shift by taking the angle value of the correlation and use that value to compensate (undo) the phase shift.

 

Correlation between two identical PSS

Correlation between a PSS and pi/3 rad shifted version

 

Following is the matlab source code that produced the result shown above. I used Matlab dsp package to calculate the cross correlation. If you don't have Matlab dsp package, try to write your own script for calculating cross correlation

    clear all;

     

    u_shift = [25 29 34];

     

    % Generate PSS for NID = 0

     

    NID = 0;

    d_u = [];

     

    for n = 0:61

        

        u = u_shift(NID+1);

        

        if n <= 30

            d = exp(-j*pi*u*n*(n+1)/63);

        else

            d = exp(-j*pi*u*(n+1)*(n+2)/63);

        end;

        

        d_u = [d_u d];

     

    end;

     

    phShift = pi/3;

    d_u_NID0 = transpose(d_u); % Original PSS

    d_u_NID0_PhaseShift = transpose(d_u .* exp(j*phShift)); % PhaseShifted PSS

     

     

    % Cross Correlation between PSS

     

    Hxcorr = dsp.Crosscorrelator;

    taps = 0:61;

    taps = taps';

     

    XCorr_0_0_Shifted = step(Hxcorr,d_u_NID0,d_u_NID0_PhaseShift);

     

    subplot(3,2,1);

    plot(real(d_u_NID0),imag(d_u_NID0),'ro','MarkerFaceColor',[1 0 0]);

    title('PSS');

     

    subplot(3,2,2);

    plot(real(d_u_NID0_PhaseShift),imag(d_u_NID0_PhaseShift),'bo','MarkerFaceColor',[0 0 1]);

    title(strcat('PSS:',num2str(phShift)));

     

    subplot(3,2,[3 4]);

    stem(taps,abs(XCorr_0_0_Shifted(62:end)),'bo','MarkerFaceColor',[0 0 1]);

    xlim([0 length(taps)]); ylim([0 100]);

    title('Abs(Corr) : PSS(NID0) and PhaseShifted PSS(NID0)');

     

    subplot(3,2,[5 6]);

    stem(taps,angle(XCorr_0_0_Shifted(62:end)),'bo','MarkerFaceColor',[0 0 1]);

    xlim([0 length(taps)]); ylim([-pi pi]);

    title('Angle(Corr): PSS(NID0) and PhaseShifted PSS(NID0)');

 

 

 

Correlation between a PSS and its Noised Version

 

This example shows the noise tolerance of PSS. The example on the left shows the correlation between a PSS and the one with 50 dB SNR (practically no Noise condition) and the example on the right shows the correlation between a PSS and the one with 10 dB SNR.

 

Correlation between two identical PSS

Correlation between a PSS and noised version

 

Following is the matlab source code that produced the result shown above. I used Matlab dsp package to calculate the cross correlation. If you don't have Matlab dsp package, try to write your own script for calculating cross correlation.

    clear all;

     

    u_shift = [25 29 34];

     

    % Generate PSS for NID = 0

     

    NID = 0;

    d_u = [];

     

    for n = 0:61

        

        u = u_shift(NID+1);

        

        if n <= 30

            d = exp(-j*pi*u*n*(n+1)/63);

        else

            d = exp(-j*pi*u*(n+1)*(n+2)/63);

        end;

        

        d_u = [d_u d];

     

    end;

     

    d_u_NID0 = transpose(d_u);

     

    % Specify SNR in dB. Try setting various different value here and see how the result changes

    SNR_dB = 10;

     

    % Get the number of symbols

    N = length(d_u_NID0);

     

    % Calculate Symbol Energy

    Eavg = sum(abs(d_u_NID0) .^ 2)/length(N);

     

    % Convert SNR (in dB) to SNR (in Linear)

    SNR_lin = 10 .^ (SNR_dB/10);

     

    % Calculate the Sigma (Standard Deviation) of AWGN

    awgnSigma = sqrt(Eavg/(2*SNR_lin));

     

    % Generate a sequence of noise with Normal Distribution and rescale it with the sigma

    awgn = awgnSigma*(randn(1,N)+j*randn(1,N));

    awgn = awgn';

     

    % Add AWGN to PSS

    d_u_NID0_Awgn = d_u_NID0 + awgn;

     

    % Cross Correlation between PSS

    Hxcorr = dsp.Crosscorrelator;

    taps = 0:61;

    taps = taps';

     

    XCorr_0_0_AWGN = step(Hxcorr,d_u_NID0,d_u_NID0_Awgn);

     

    subplot(3,2,1);

    plot(real(d_u_NID0),imag(d_u_NID0),'ro','MarkerFaceColor',[1 0 0]);

    axis([-2 2 -2 2]);

    title('PSS');

     

    subplot(3,2,2);

    plot(real(d_u_NID0),imag(d_u_NID0),'bo', ...

        'MarkerFaceColor',[0 0 1]);

    hold on;

    plot(real(d_u_NID0_Awgn),imag(d_u_NID0_Awgn),'bo', ...

        'MarkerFaceColor',[0 0 0],'MarkerSize',1);

    axis([-2 2 -2 2]);

    title(strcat('PSS:',num2str(SNR_dB)));

    hold off;

     

    subplot(3,2,[3 4]);

    stem(taps,abs(XCorr_0_0_AWGN(62:end)),'bo','MarkerFaceColor',[0 0 1]);

    xlim([0 length(taps)]); ylim([0 100]);

    title('Abs(Corr) : PSS(NID0) and Noised PSS(NID0)');

     

    subplot(3,2,[5 6]);

    stem(taps,angle(XCorr_0_0_AWGN(62:end)),'bo','MarkerFaceColor',[0 0 1]);

    xlim([0 length(taps)]); ylim([-pi pi]);

    title('Angle(Corr): PSS(NID0) and Noised PSS(NID0)');

     

 

 

 

PSS detection in Action

 

Primary Synchronization Signal (PSS) detection is conducted to find the start of a frame and establish frequency synchronization. It's done by correlating the received signal with locally generated PSS sequences. Two main methods can be used: time-domain correlation, which directly compares the signal with the PSS sequence, or frequency-domain correlation, which performs the correlation done in frequency domain as described below.

Basically both method shown here is a sliding window which slides along the sequence of I/Q data recieved by the reciever (UE)

 

 

Frequeny Domain Correlation Method

 

 

 

Description of what's happening at each step in the illustration goes as follows :

    (A) Fast Fourier Transform (FFT) of IQ in a window (chunk): This step converts the time-domain IQ signals in a window, or chunk, into the frequency domain.

    (B) Take out bins for PSS: After the FFT, the frequency bins corresponding to the PSS signal are extracted. In LTE, these are the central 62 frequency bins around DC, which contain the PSS signal.

    (C) Check Correlation with 3 generated PSS: In this step, the extracted frequency bins are correlated with the three possible PSS sequences that are locally generated and transformed into the frequency domain.

    (D) Pick the largest value of the 3 correlations: Once the correlation values are obtained for all three PSS sequences, the maximum correlation value is chosen. This step identifies the specific PSS sequence (out of the three possible ones) that matches the received signal.

    (E) Is the result greater than the current max? This step compares the maximum correlation value from step (D) with a predefined threshold or the maximum correlation value from the previous chunk.

    (F) Update the max value: If the maximum correlation value from step (D) is larger than the current maximum, this new value is set as the maximum. This updated value then serves as the threshold for the next chunk. If the PSS is successfully detected, it means the start of the LTE frame is found and frequency synchronization can be established.

 

 

Time Domain Correlation Method

 

 

Description of what's happening at each step in the illustration goes as follows :

    (1) Check Correlation with 3 generated PSS: In this step, the incoming time-domain IQ samples are correlated with the three possible PSS sequences. These sequences are originally generated in the frequency domain as defined by the LTE standard, then transformed into the time domain for this correlation process. (NOTE : The way in which the PSS is converted to time domain sequence is shown in the box (N))

    (2) Pick the largest value of the 3 correlations: Once the correlation values are obtained for all three PSS sequences, the maximum correlation value is chosen. This step identifies the specific PSS sequence (out of the three possible ones) that matches the received signal.

    (3) Is the result greater than the current max? This step compares the maximum correlation value from step (2) with a predefined threshold or the maximum correlation value from the previous chunk.

    (4) Update the max value: If the maximum correlation value from step (2) is larger than the current maximum, this new value is set as the maximum. This updated value then serves as the threshold for the next chunk. If the PSS is successfully detected, it means the start of the LTE frame is found and time synchronization can be established.

 

 

Source Code in srsUE

 

You can check out and see how real life implementation of this process in srsUE source code. Followings are the list of functions you would refer to this process (this is based on the code that I downloaded for srs github in Jul 2023)

  • \lib\src\phy\sync\pss.c -> int srsran_pss_find_pss()
    • \lib\src\phy\utils\convolution.c\srsran_conv_fft_cc_run_opt()
    • \lib\src\phy\sync\pss.c -> int srsran_pss_init_fft_offset_decim()
    • \lib\src\phy\sync\pss.c -> int srsran_pss_resize()
      • \lib\src\phy\sync\pss.c -> int srsran_pss_init_N_id_2()
        • \lib\src\phy\sync\pss.c -> int srsran_pss_generate()
    • \lib\src\phy\utils\convolution.c\srsran_conv_fft_cc_run_opt()

 

 

 

Frequency Adjustment with PSS

 

In addition fo finding the timing sync of a subframe / OFDM symbol, the detected PSS provide a very important information.

 

 

Frequency Offset Estimation

 

By comparing the expected PSS (ideal PSS) and the received PSS, we can estimate Frequency Offset. This error can be used to adjust other signals (e.g, SSS) before processing. This offset estimation is crucial in wireless communication for correcting carrier frequency offsets introduced by transmitter-receiver oscillator frequency mismatches or the Doppler effect.

 

The way to calculate (estimate) the frequency offset can be done by comparing the PSS sequence received and the PSS sequence (i.e, expected sequence) that is generated by the 3GPP algorithm. This comparison and frequency offset can be done in 3 steps as follows.

    Step 1 :  Compute the phase difference between each corresponding elements of the received sequence and the expected sequence

    Step 2 :  Sum all of the phase difference in step 2. Let's call this as total phase difference

    Step 3 :  Convert the total phase difference into frequency offset

This can be explained in more detail with a simple Python script as follows.

 

def frequency_offset_estimation(received_pss, expected_pss, sample_rate = 30.72e6):

    phase_difference = np.angle(np.dot(received_pss, np.conj(expected_pss)))

    

    # Time duration for transmitting PSS, in seconds

    time_duration_pss = 62 / sample_rate

 

    # Convert phase difference to frequency offset in Hz

    frequency_offset = (phase_difference / (2 * np.pi)) / time_duration_pss

 

    return frequency_offset

 

Let me look into the code itself.

 

phase_difference = np.angle(np.dot(received_pss, np.conj(expected_pss)))

 

This corresponds to Step 1 and Step 2 described above. np.dot(received_pss, np.conj(expected_pss) can be expressed in terms of angular value as follows.

        angle( sum(angle(received_pss) - angle(expected_pss)) )

The minus '-' before angle(expected_pss) is done by taking conj( ) of expected_pss

 

 

frequency_offset = (phase_difference / (2 * np.pi)) / time_duration_pss

 

This line translates the calculated phase difference into the frequency domain to estimate the frequency offset. The factor of 2*πi converts the phase from radians to cycles, 62 is the number of subcarriers used for the PSS in LTE, and sample_rate is the rate at which the PSS signal was sampled.

Phase and frequency are directly related: the change in phase over time is frequency. In the frequency offset estimation function, the phase difference is divided by the time duration of the symbol (which is the number of subcarriers divided by the sample rate), giving us the rate of change of phase, or frequency offset.

In other words, the equation calculates the average change in phase per sample, which is then converted to Hz to represent the frequency offset. This frequency offset represents the difference in frequency between the received PSS and the expected PSS

 

 

Frequency Offset Compensation

 

Once the frequency offset is calculated (estimated) as explained above, you can use the value to compensate (correct) the frequency offset of other signal (data).

The basic idea is as follows :

    Step 1 : Convert the frequency offset to phase shift and generate a rotating factor.

    Step 2 : Rotate (compensate) the given signal using the rotating factor

This can be explained by a simple python code as below :

 

def correct_frequency_offset(signal, frequency_offset, sample_rate):

 

    signal = np.array(signal, dtype=np.complex128)

    time = len(signal) / sample_rate

    correction = np.exp(-1j * 2 * np.pi * frequency_offset * time)

    corrected_signal = signal * correction

 

    return corrected_signal

 

 

Let me look into the code itself.

 

time = len(signal) / sample_rate

 

The time is calculated as the total duration of the signal in seconds. This is done by dividing the number of samples in the signal (len(signal)) by the sample rate (sample_rate).

 

 

correction = np.exp(-1j * 2 * np.pi * frequency_offset * time)

 

The value correction here is the correction factor. This is a complex-valued factor that, when multiplied with the original signal, will correct the frequency offset. The correction factor is computed using the formula for a complex exponential, np.exp(-1j * 2 * np.pi * frequency_offset * time),

  • The -1j in the exponential formula represents a negative imaginary unit, and it's used to rotate the signal in the opposite direction of the offset.
  • The 2 * np.pi * frequency_offset term inside the exp() represents the angular frequency in radians per second.
  • The time is the total duration of the signal in seconds.

 

 

corrected_signal = signal * correction

 

The corrected_signal is then computed by multiplying the original signal with the correction factor. This rotates the signal by the required amount to correct the frequency offset.

 

 

NOTE :  The method implemented here is a kind of simplified method. It assumes that all the samples in the data has the same amount of frequency offset. Typically, frequency offset correction involves applying a different correction factor to each sample in the signal (i.e., each sample gets rotated by a different amount), rather than applying the same correction factor to all samples, as is done here. This can be done by computing the correction factor as a function of time for each sample. But this approach is not used in this specific function.

However, if the frequency offset is not constant over the time span of the signal, this method might not fully correct the frequency offset. This is because the method assumes a constant frequency offset over the entire time span of the signal.

 

 

 

Reference :

 

[1] Synchronization and Cell Search