function [ofdmDemod,cpe,peg,pilotGain] = heTrackingOFDMDemodulate(rxData,chanEst,numOFDMSym,cfgHE,cfgTrack,varargin) %heTrackingOFDMDemodulate OFDM demodulation with pilot tracking % % Note: This is an internal undocumented function and its API and/or % functionality may change in subsequent releases. % % [DATASYM,CPE,PEG,PILOTGAIN] = trackingOFDMDemodulate(RXDATA,CHANEST, % NUMOFDMSYM, CFGSU,CFGTRACK) performs OFDM demodulation of data % symbols with optional pilot tracking. % % DATASYM is a complex Nsd-by-Nsym-by-Nr array containing the demodulated % symbols at data carrying subcarriers. Nsd represents the number of data % subcarriers, Nsym represents the number of OFDM symbols in the Data % field, and Nr represents the number of receiver antennas. % % CPE is a column vector of length Nsym containing the common phase error % between each received and expected OFDM symbol. % % PEG is a column vector of length Nsym containing the phase error % gradient per OFDM symbol in degrees per subcarrier. This error is % caused by a sample rate offset between transmitter and receiver. % % PILOTGAIN is an Nsym-by-Nsp array containing the gain of pilots. Nsp is % the number of pilot subcarriers. % % RXDATA is the received time-domain Data field signal, specified as an % Ns-by-Nr matrix of real or complex values. Ns represents the number of % time-domain samples in the Data field and Nr represents the number of % receive antennas. Ns can be greater than the Data field length; in this % case additional samples at the end of RXDATA, if not required, are not % used. When sample rate offset tracking is enabled using the optional % CFTRACK argument, additional samples may be required in RXDATA. This is % to allow for the receiver running at a higher sample rate than the % transmitter and therefore more samples being required. % % CHANEST is a complex Nst-by-Nsts-by-Nr array containing the channel % gains at active subcarriers, or a Nsp-by-Nsts-by-Nr array containing % the channel gains at pilot subcarriers. Nsts is the number of % space-time streams. % % NUMOFDMSYM is the number of OFDM symbols expected. % % CFGSU is a format configuration object of type wlanHESUConfig or % wlanHERecoveryConfig. % % CFGTRACK is a pilot tracking configuration structure. % % [...] = trackingOFDMDemodulate(RXDATA,CHANEST,NUMOFDMSYM,CFGMU, % CFGTRACK,RUNUMBER) performs OFDM demodulation of data symbols with % optional pilot tracking for a multi-user configuration. As a % multi-user configuration is provided and resource unit number is % required. % % CFGMU is the format configuration object of type wlanHEMUConfig. % % RUNUMBER is the RU (resource unit) number. % Copyright 2018-2020 The MathWorks, Inc. cpe = nan(numOFDMSym,1); % Initialize in case not calculated peg = nan(numOFDMSym,1); % Initialize in case not calculated pilotGain = nan(numOFDMSym,1); if strcmp(cfgTrack.PilotTracking,'Joint') % Perform joint measurement and optionally correction % Reduce the size of the averaging window if it exceeds the number of % OFDM symbols. If it is even then use the largest odd window we can. if cfgTrack.PilotTrackingWindow>numOFDMSym cfgTrack.PilotTrackingWindow = numOFDMSym-(rem(numOFDMSym,2)==0); end [ofdmDemod,peg,cpe,pilotGain] = demodulateWithPhaseTracking(rxData,chanEst,numOFDMSym,cfgHE,cfgTrack,varargin{:}); else % OFDM demodulate only when no joint tracking if isa(cfgHE,'wlanHERecoveryConfig') ofdmDemod = wlanHEDemodulate(rxData,'HE-Data',cfgHE.ChannelBandwidth,cfgHE.GuardInterval, ... [cfgHE.RUSize cfgHE.RUIndex],'OFDMSymbolOffset',cfgTrack.OFDMSymbolOffset); else ofdmDemod = wlanHEDemodulate(rxData,'HE-Data',cfgHE,varargin{:},'OFDMSymbolOffset',cfgTrack.OFDMSymbolOffset); end ofdmDemod = ofdmDemod(:,1:numOFDMSym,:); % Truncate output as extra samples may be passed if strcmp(cfgTrack.PilotTracking,'CPE') % Pilot phase tracking [ofdmDemod,cpe] = heCommonPhaseErrorTracking(ofdmDemod,chanEst,cfgHE,varargin{:}); cpe = cpe.'; % Permute to return end end end function [ofdmDemod,peg,cpe,pilotGain] = demodulateWithPhaseTracking(rxData,chanEst,numOFDMSym,cfg,cfgTrack,varargin) if isa(cfg,'wlanHERecoveryConfig') pktFormat = cfg.PacketFormat; if strcmp(pktFormat,'HE-MU') numSpaceTimeStreamsPerRU = cfg.RUTotalSpaceTimeStreams; s = getSIGBLength(cfg); numHESIGB = s.NumSIGBSymbols; else % SU or EXT_SU numSpaceTimeStreamsPerRU = cfg.NumSpaceTimeStreams; numHESIGB = 0; end ruSize = cfg.RUSize; ofdmInfo = wlanHEOFDMInfo('HE-Data',cfg.ChannelBandwidth,cfg.GuardInterval,[cfg.RUSize cfg.RUIndex]); else pktFormat = packetFormat(cfg); allocInfo = ruInfo(cfg); if isa(cfg,'wlanHEMUConfig') ruNumber = varargin{1}; ofdmInfo = wlanHEOFDMInfo('HE-Data',cfg,ruNumber); sigbInfo = wlan.internal.heSIGBCodingInfo(cfg); numHESIGB = sigbInfo.NumSymbols; numSpaceTimeStreamsPerRU = allocInfo.NumSpaceTimeStreamsPerRU(ruNumber); ruSize = allocInfo.RUSizes(ruNumber); else % SU or EXT_SU ofdmInfo = wlanHEOFDMInfo('HE-Data',cfg); numHESIGB = 0; numSpaceTimeStreamsPerRU = allocInfo.NumSpaceTimeStreamsPerRU; ruSize = allocInfo.RUSizes; end end if strcmp(pktFormat,'HE-EXT-SU') numHESIGA = 4; else % SU or MU numHESIGA = 2; end % OFDM demodulate configuration prmStr = struct; prmStr.NumReceiveAntennas = size(rxData,2); prmStr.FFTLength = ofdmInfo.FFTLength; prmStr.NumSymbols = 1; prmStr.SymbolOffset = cfgTrack.OFDMSymbolOffset*ofdmInfo.CPLength(1); prmStr.CyclicPrefixLength = ofdmInfo.CPLength(1); N = ofdmInfo.FFTLength; % FFT length is samples Ng = ofdmInfo.CPLength; % Number of samples in GI Ns = (N+Ng); % Number of samples per symbols kst = ofdmInfo.ActiveFrequencyIndices; % Indices of all active subcarriers kd = kst(ofdmInfo.DataIndices); % Indices of data carrying subcarriers kp = kst(ofdmInfo.PilotIndices); % Indices of pilot carrying subcarriers Nd = numel(kd); % Number of data carrying subcarriers Np = numel(kp); % Number of pilot carrying subcarriers Nr = size(chanEst,3); % Number of receive antennas n = (0:numOFDMSym-1); z = 2+numHESIGA+numHESIGB; % Pilot symbol offset if numel(ofdmInfo.PilotIndices)==size(chanEst,1) % Assume channel estimate is only for pilots chanEstPilots = chanEst; else % Otherwise extract pilots from channel estimate chanEstPilots = chanEst(ofdmInfo.PilotIndices,:,:); end nsts = min(numSpaceTimeStreamsPerRU,size(chanEstPilots,2)); % Allow for single-stream or MIMO pilots refPilots = wlan.internal.hePilots(ruSize,nsts,n,z); % Reshape for computation chanEstPilotsR = permute(chanEstPilots,[3 2 1]); refPilotsR = permute(refPilots,[3 1 2]); % Generate reference pilots % Calculate expected pilot values pilotExp = complex(zeros(Np,numOFDMSym)); for n = 1:numOFDMSym for p=1:Np pilotExp(p,n) = sum(reshape(chanEstPilotsR(:,:,p)*refPilotsR(:,p,n),[],1)); end end ofdmDemodPilots = complex(zeros(Np,numOFDMSym,Nr)); ofdmDemod = complex(zeros(Nd+Np,numOFDMSym,Nr)); perr = complex(zeros(Np,numOFDMSym)); % Pilot error delta = zeros(numOFDMSym,1); omega = zeros(numOFDMSym,1); skipDupStore = zeros(numOFDMSym,1); skipdup = 0; nD = 0; % Number of OFDM symbols demodulated for n = 1:numOFDMSym % Get index of samples to demodulate in current symbol skipDupStore(n) = skipdup; idx = (n-1)*Ns+(1:Ns)+skipdup; if any(idx>size(rxData,1)) % Break from loop if we run out of data coder.internal.warning('wlan:trackingOFDMDemodulate:NotEnoughSamples',numOFDMSym,n-1); break; end % OFDM demodulation demod = commonDemod(rxData(idx,:),ofdmInfo,prmStr); ofdmDemodPilots(:,n,:) = demod(ofdmInfo.PilotIndices,1,1:Nr); % for codegen ofdmDemod(:,n,:) = demod(:,1,1:Nr); % for codegen % Calculate pilot error ofdmDemodPilotsR = permute(ofdmDemodPilots(:,n,:),[2 3 1]); for p = 1:Np perr(p,n) = reshape(conj(ofdmDemodPilotsR(1,:,p))*chanEstPilotsR(:,:,p)*refPilotsR(:,p,n),1,1); end % Average pilots over time window perridx = max((n-cfgTrack.PilotTrackingWindow+1),1):n; % Find indices which span across a skip/dup spanSkipDup = perridx(skipDupStore(perridx)~=skipDupStore(perridx(end))); if any(spanSkipDup) % Remove phase shift offset caused by skip/dup and average skipdupVal = (skipDupStore(perridx)-skipDupStore(perridx(end))).'; perrav = sum(perr(:,perridx).*exp(1i*2*pi*bsxfun(@times,skipdupVal,kp)/N),2); else perrav = sum(perr(:,perridx),2); end if n>1 % Subtract the previous common phase from the current to avoid % needing to wrap (use phasor to avoid angles wrapping across % pilots before subtraction) perrav = perrav*exp(-1i*omega(n-1)); end % Least square estimation with covariance estimate per symbol j = lscov([kp ones(size(kp))],angle(perrav),abs(perrav)); delta(n) = j(1); % Time offset % If subtracting previous common phase then add it back on to % common phase error if n>1 omega(n) = j(2)+omega(n-1); else omega(n) = j(2); end % Skip or duplicate a sample in the next OFDM symbol if % required if delta(n)>=(2*pi/N)*0.9 skipdup = skipdup+1; % Skip elseif delta(n)<=-(2*pi/N)*0.9 skipdup = skipdup-1; % Duplicate end nD = n; % Record number of demodulated symbols end pilotGain = movmean(abs(perr).',cfgTrack.PilotTrackingWindow/2); % The averaging causes a delay which we correct for before applying % correction delay = (cfgTrack.PilotTrackingWindow-1)/2; % When a skip-dup occurred we changed the phase to allow averaging over % the skip/dup. Now correct for any phase change applied skipindTmp = bsxfun(@plus,(find((diff(skipDupStore))==1)+1),(0:delay-1)); skipind = skipindTmp(:); % for codegen dupindTmp = bsxfun(@plus,(find((diff(skipDupStore))==-1)+1),(0:delay-1)); dupind = dupindTmp(:); % for codegen skipCorrIdx = skipind(skipind<=numOFDMSym); delta(skipCorrIdx) = delta(skipCorrIdx)+2*pi/N; dupCorrIdx = dupind(dupind<=numOFDMSym); delta(dupCorrIdx) = delta(dupCorrIdx)-2*pi/N; % Use shrinking window at end of waveform to average pilots and account % for delay keepIdx = setdiff(1:numOFDMSym,2:2:cfgTrack.PilotTrackingWindow); % Remove even averages at start when growing window deltaTmp = [delta(keepIdx); zeros(delay,1)]; omegaTmp = [omega(keepIdx); zeros(delay,1)]; extDelta = zeros(delay,1); for i = 1:delay % Remove difference of phases due to skip/dup over averaging window skipdupVal = (skipDupStore(nD-(cfgTrack.PilotTrackingWindow-2*i)+1:nD)-skipDupStore(nD)).'; perrav = sum(perr(:,nD-(cfgTrack.PilotTrackingWindow-2*i)+1:nD).*exp(1i*2*pi.*bsxfun(@times,skipdupVal,kp)/N),2); % Remove previous CPE before LS estimation perrav = perrav*exp(-1i*omegaTmp(nD-delay+i-1)); % Reapply phase offset removed for averaging due to skip/dup angleperrav = angle(perrav)-skipdupVal(delay-i+1)*2*pi.*kp/N; % Least-square estimation jt = lscov([kp ones(size(kp))],angleperrav,abs(perrav)); extDelta(i) = jt(1); omegaTmp(nD-delay+i) = jt(2)+omegaTmp(nD-delay+i-1); % Add previous CPE end delta(1:nD) = [deltaTmp(1:nD-delay); extDelta]; omega = omegaTmp; % Apply correction if requested if strcmp(cfgTrack.PilotTracking,'Joint') % Correction for timing corrst = exp(1i*bsxfun(@times,delta.',kst)); % Correction for phase corrst = bsxfun(@times,corrst,exp(1i*omega.')); % Apply per symbol correction of subcarriers ofdmDemod(:,1:nD,:) = bsxfun(@times,ofdmDemod(:,1:nD,:),corrst(:,1:nD)); end % Return estimate of impairments cpe = -omega; peg = -delta; % Perform pilot gain tracking if requested if cfgTrack.PilotGainTracking == true normGain = pilotGain./pilotGain(1,:); weightedAverages = pilotGain(1,:)./sum(pilotGain(1,:)); gainTracking = sum(normGain.*weightedAverages,2); ofdmDemod = ofdmDemod./gainTracking.'; end end function x = lscov(A,b,V) % Weights given, scale rows of design matrix and response. D = sqrt(V(:)); A(:,1) = A(:,1).*D; A(:,2) = A(:,2).*D; b = b.*D; % Factor the design matrix, incorporate covariances or weights into the % system of equations, and transform the response vector. [Q,R] = qr(A,0); z = Q'*b; % Compute the LS coefficients x = real(R\z); end function demod = commonDemod(rx,cfgOFDM,prmStr) fftout = comm.internal.ofdm.demodulate(rx,prmStr); % Extract active subcarriers from full FFT demod = fftout(cfgOFDM.ActiveFFTIndices,:,:); % Scale by number of active tones and FFT length demod = demod*sqrt(cfgOFDM.NumTones)/cfgOFDM.FFTLength; end