function [chanEstRU,varargout] = heLTFChannelEstimate(demodHELTFRU,cfg,varargin) %heLTFChannelEstimate Channel estimation using HE-LTF % CHANESTRU = heLTFChannelEstimate(RXSYM,CFGHE) returns the estimated % channel between all space-time streams and receive antennas using % HE-LTF of an HE single user, extended range single user, multi-user or % trigger-based (HE TB) packet. The channel estimate includes the % effect of the applied spatial mapping matrix and cyclic shifts at the % transmitter. If HE-LTF compression is used, linear interpolation is % performed to create a channel estimate for all subcarriers. % % CHANESTRU is an array characterizing the estimated channel for the data % and pilot subcarriers. EST is a complex Nst-by-Nsts-by-Nr array % characterizing the estimated channel for the data and pilot % subcarriers, where Nst is the number of occupied subcarriers, Nsts is % the total number of space-time streams, and Nr is the number of receive % antennas. If CFGHE is a MU configuration, then the channel estimate for % all RUs is returned. % % RXSYM is a complex Nst-by-Nsym-by-Nr array containing demodulated % concatenated HE-LTF. Nsym is the number of demodulated HE-LTF symbols. % % CFGHE is a format configuration object of type wlanHESUConfig % wlanHEMUConfig, wlanHETBConfig, heTBSystemConfig or wlanHERecoveryConfig. % % CHANESTRU = heLTFChannelEstimate(RXSYM,CFGMU,RUOFINTEREST) returns the % channel estimate for the RU of interest index RUOFINTEREST for a % multi-user configuration. CFGMU is of type wlanHEMUConfig or % heTBSystemConfig. If not provided the default is 1. % % [...,CHANESTSSPILOTS] = heLTFChannelEstimate(...) additionally returns % a Nsp-by-Nsym-by-Nr array characterizing the estimated channel for % pilot subcarrier locations for each symbol, assuming one space-time % stream at the transmitter. % % Examples: % % Decode the HE data field for each user in and OFDMA and MU-MIMO % % transmission with a fading channel model. Estimate the channel for % % each user. % % % Create packet configuration % allocationIndex = [192 193]; % Two 242 RUs, the second with 2 users % cfg = wlanHEMUConfig(allocationIndex); % cfg.NumTransmitAntennas = 2; % cfg.User{1}.NumSpaceTimeStreams = 2; % % % Generate MU waveform % txWaveform = wlanWaveformGenerator([1;0;0;1],cfg); % % % Channel and receiver per user % ind = wlanFieldIndices(cfg); % allocationInfo = ruInfo(cfg); % for ruIdx = 1:allocationInfo.NumRUs % for userIdx = 1:allocationInfo.NumUsersPerRU(ruIdx) % % Add channel and noise % snr = 20; % channel = wlanTGaxChannel; % channel.NumTransmitAntennas = 2; % channel.NumReceiveAntennas = 2; % channel.SampleRate = wlanSampleRate(cfg); % rxWaveform = awgn(channel([txWaveform; zeros(10,2)]),snr); % % % Synchronize % rxWaveform = rxWaveform(1+4:end,:); % % % Extract and OFDM demodulate the HE-LTF for the RU of % % interest % rxHETLF = rxWaveform(ind.HELTF(1): ind.HELTF(2),:); % demodHELTF = wlanHEDemodulate(rxHETLF,'HE-LTF',cfg,ruIdx); % % % Channel estimate for RU of interest % chanEst = heLTFChannelEstimate(demodHELTF,cfg,ruIdx); % % % Extract and OFDM demodulate the data field for the RU of % % interest % rxData = rxWaveform(ind.HEData(1):ind.HEData(2),:); % demodData = wlanHEDemodulate(rxData,'HE-Data',cfg,ruIdx); % % % Equalize data symbols - extract the space-time streams for % % the user of interest after equalization % nVar = 10^-(snr/10); % [eqSym,csi] = heEqualizeCombine(demodData,chanEst,nVar, ... % cfg,userIdx); % % % Discard pilot carriers and decode % info = wlanHEOFDMInfo('HE-Data',cfg,ruIdx); % rxBits = wlanHEDataBitRecover(eqSym(info.DataIndices,:,:), ... % nVar,csi(info.DataIndices,:),cfg,userIdx); % end % end % Copyright 2017-2019 The MathWorks, Inc. %#codegen if nargin>2 ruOfInterest = varargin{1}; else ruOfInterest = 1; end % Validate the format configuration object is a valid type validateattributes(cfg,{'wlanHESUConfig','wlanHEMUConfig','wlanHETBConfig','heTBSystemConfig','wlanHERecoveryConfig'},{'scalar'},mfilename,'format configuration object'); % Get allocation information if isa(cfg,'wlanHERecoveryConfig') ruSizeRU = cfg.RUSize; ruIndexRU = cfg.RUIndex; pktFormat = cfg.PacketFormat; if strcmp(pktFormat,'HE-MU') numSTSRU = cfg.RUTotalSpaceTimeStreams; else % SU, EXT SU numSTSRU = cfg.NumSpaceTimeStreams; end else allocInfo = ruInfo(cfg); coder.internal.errorIf(ruOfInterest>allocInfo.NumRUs,'wlan:he:InvalidRUOfInterest',ruOfInterest,allocInfo.NumRUs); ruSizeRU = allocInfo.RUSizes(ruOfInterest); ruIndexRU = allocInfo.RUIndices(ruOfInterest); numSTSRU = allocInfo.NumSpaceTimeStreamsPerRU(ruOfInterest); pktFormat = packetFormat(cfg); end % Validate symbol type validateattributes(demodHELTFRU,{'single','double'},{'3d'},mfilename,'HE-LTF OFDM symbol(s)'); [numST,numLTF,numRx] = size(demodHELTFRU); tac = wlan.internal.heRUToneAllocationConstants(ruSizeRU); coder.internal.errorIf(numST~=tac.NST,'wlan:wlanChannelEstimate:IncorrectNumSC',tac.NST,numST); ofdmInfo = wlanHEOFDMInfo('HE-LTF',cfg.ChannelBandwidth,cfg.GuardInterval,[ruSizeRU ruIndexRU]); if numLTF==0 chanEstRU = zeros(numST,numSTSRU,numRx); varargout{1} = zeros(numel(ofdmInfo.PilotIndices),numLTF,numRx); % For codegen return; end minNumLTF = wlan.internal.numVHTLTFSymbols(numSTSRU); coder.internal.errorIf(numLTF1 chanEstRU = chanEstInterp(chanEstRU,cbw,N_HE_LTF_Mode,ruSizeRU,ruIndexRU); end else % MIMO channel estimation as per Perahia, Eldad, and Robert Stacey. % Next Generation Wireless LANs: 802.11 n and 802.11 ac. Cambridge % University Press, 2013, page 100, Equation 4.39. % Remove orthogonal sequence across subcarriers (if used) if isaTBConfig && cfg.SingleStreamPilots==false % Only perform channel estimate for non-pilot subcarriers as pilots % are single stream kMIMO = kRU; % All subcarriers MIMO estimates mimoInd = 1:numST; chanEstRUMIMO = wlan.internal.mimoChannelEstimate(demodHELTFRU,HELTFRU,numSTSRU); chanEstRUMIMO = removeOrthogonalSequence(chanEstRUMIMO,numSTSRU,kRU,N_HE_LTF_Mode); else % Only perform channel estimate for non-pilot subcarriers as pilots % are single stream mimoInd = ofdmInfo.DataIndices; kMIMO = kRU(mimoInd); % Only data subcarriers MIMO estimates chanEstRUMIMO = wlan.internal.mimoChannelEstimate(demodHELTFRU(ofdmInfo.DataIndices,:,:),HELTFRU(mimoInd),numSTSRU); end % Undo cyclic shift for each STS before averaging and interpolation nfft = (cbw/20)*256; numSTSTotal = size(chanEstRUMIMO,2); csh = wlan.internal.getCyclicShiftVal('VHT',numSTSTotal,cbw); chanEstRUMIMO = wlan.internal.cyclicShiftChannelEstimate(chanEstRUMIMO,-csh,nfft,kMIMO); % Interpolate over pilot locations, and any compressed subcarriers chanEstRU = chanEstInterp(chanEstRUMIMO,cbw,N_HE_LTF_Mode,ruSizeRU,ruIndexRU,mimoInd); % Re-apply cyclic shift after interpolation chanEstRU = wlan.internal.cyclicShiftChannelEstimate(chanEstRU,csh,nfft,kRU); end % If extended range SU, then the HE-LTF are boosted by sqrt(2). If we % don't remove this at demodulation then we must de-scale the channel % estimate as the data field is not scaled. if strcmp(pktFormat,'HE-EXT-SU') eta = 1/sqrt(2); else eta = 1; end chanEstRU = chanEstRU*eta; % Scale for HE-EXT-SU % Channel estimate for pilots if nargout>1 if isaTBConfig && cfg.SingleStreamPilots==false % Create single stream from MIMO pilot estimates by summing across % space-time streams (2nd dimension) varargout{1} = sum(chanEstRU(ofdmInfo.PilotIndices,:,:),2); else % Channel estimate for single-stream pilots Pheltf = wlan.internal.mappingMatrix(numLTF); R = Pheltf(1,1:numLTF); % R matrix changes pilot polarity per symbol % Estimate the channel at pilot subcarriers accounting for polarity chanEstSSPilots = bsxfun(@rdivide,demodHELTFRU(ofdmInfo.PilotIndices,:,:),bsxfun(@times,HELTFRU(ofdmInfo.PilotIndices),R)); varargout{1} = chanEstSSPilots*eta; % Scale for HE_EXT_SU end end end function chanEstRUInterp = chanEstInterp(chanEstRU,cbw,N_HE_LTF_Mode,ruSize,ruIndex,varargin) % Interpolate over pilot locations and compressed subcarriers Nfft = 256*cbw/20; % Get the subcarrier indices within the FFT for the channel estimate % input kAct = wlan.internal.heRUSubcarrierIndices(cbw,ruSize,ruIndex)+Nfft/2+1; % If the channelEstRU is not the entire RU, then we need to make sure % we know the subcarrier indices, so use the ruInd input. For example % this allows us to pass in only the data subcarriers. if nargin>5 ruInd = varargin{1}; kChanEstInputs = kAct(ruInd); else % Assume chanEstRU is the whole RU kChanEstInputs = kAct; end % Get the indices within the FFT which contain actual estimates % (excluding the guard bands). This is how the pattern is structured kAll = 1:N_HE_LTF_Mode:Nfft; % Find the subcarrier indices within the FFT which contain actual data % within the channel estimate input (kToInterp) and the indices of % these within the chanEstDataRU input array (toInterpInd) [kToInterp,toInterpInd] = intersect(kChanEstInputs,kAll); % Interpolate and extrapolate over all RU subcarrier indices to % interpolate over compressed region and pilots magPart = interp1(kToInterp.',abs(chanEstRU(toInterpInd,:,:)),kAct,'linear','extrap'); phasePart = interp1(kToInterp.',unwrap(angle(chanEstRU(toInterpInd,:,:))),kAct,'linear','extrap'); [realPart,imagPart] = pol2cart(phasePart,magPart); chanEstRUInterp = complex(realPart,imagPart); end function chanEstRUData = removeOrthogonalSequence(chanEstRUData,numSTSRU,k,N_HE_LTF_Mode) % Remove the orthogonal sequence across subcarriers M = 0; % Assume space-time streams of all users in estimate m = 1:numSTSRU; Pheltf = wlan.internal.mappingMatrix(8); seq = Pheltf(M+m,mod(ceil(k/N_HE_LTF_Mode)-1,8)+1).'; % Nsts-by-Nst chanEstRUData = chanEstRUData./seq; end