Hardware Array Data Collection and Simulation
Description
In this example, we use an educational hardware phased array platform from Analog Devices, Inc (ADI) to collect real world data and compare with simulated data using the Phased Array System Toolbox.
We first perform a simple calibration routine to set the analog and digital gain and phase of each antenna element and subarray channel. We then compare the pattern of the uncalibrated and calibrated antenna to the ideal pattern predicted by a model of the array using Phased Array System Toolbox.
After calibrating the antenna, we look at the agreement between simulated data and measured data in various situations, such as when certain antenna elements are disabled to create a sparse array, when the antenna pattern is tapered to reduce the sidelobe level, and when a null cancellation technique is implemented for antenna steering.
Setup Description
The hardware setup used is modelled using the Phased Array System Toolbox. The setup consists of a simple isotropic CW transmitter as well as a phased array receiver which is made up of two subarrays, both containing four antenna elements. A picture of the receiver, transmitter, and overall setup are shown below.
Note that each antenna element is comprised of four patch antennas, so what looks like 32 receive antenna elements is actually 32 / 4 = 8 antenna elements.
This wiki page from ADI contains more information on getting the phased array board used for this example up and running.
Transmit Antenna Model
A simple CW transmitter is used to generate the signal measured by the antenna array. A model of the transmitter is created in this section. The pattern of the transmit antenna is estimated as isotropic.
fc = 10.4115e9; txelement = phased.IsotropicAntennaElement; radiator = phased.Radiator("Sensor",txelement,"OperatingFrequency",fc);
Receive Antenna Model
A model of the phased array used to collect data for this example is described and constructed in this section. For a detailed schematic of this array, see [1].
The phased array antenna operates in a portion of the X band from 10.0 to 10.5 GHz.
frange = [10.0e9 10.5e9];
Sampling occurs at 30 MHz, 1024 samples are collected for each frame. The Analog to Digital Converter (ADC) is a 12-bit device, so the collected samples have a range of values from -2^11 to 2^11.
sampleRate = 30e6; nsamples = 1024; adcbits = 12; maxdatavalue = 2^(adcbits-1);
Each antenna element in the array is modelled as cosine. Each antenna element channel has a Low Noise Amplifier (LNA), a phase shifter, and an adjustable gain. The default phase shift and gain for each channel are the values that are adjusted during calibration.
element = phased.CosineAntennaElement('FrequencyRange',frange);
The array consists of two subarrays made up of four antenna elements each. The element spacing is half of the shortest wavelength in the frequency range. Use a ULA to model the antenna elements.
lambdaRange = freq2wavelen(frange); spacing = lambdaRange(2)/2; nSubarrayElements = 4; nSubarrays = 2; array = phased.ULA('Element',element,'NumElements',nSubarrayElements*nSubarrays,'ElementSpacing',spacing);
The ideal receiver is characterized based on the Low Noise Amplifier (LNA) found in the ADI Phaser, the ADL8107 [2]. This LNA has a gain of 24 dB, an output third-order intercept point (OIP3) of 29 dBm, and a Noise figure of 1.3 dB.
receiverGain = 24; receiverOip3 = 29; receiverNf = 1.3; idealReceiver = phased.Receiver(GainMethod="Cubic polynomial",Gain=receiverGain,OIP3=receiverOip3,NoiseMethod="Noise figure",NoiseFigure=receiverNf,SampleRate=sampleRate);
The gain curve for the receiver is demonstrated below.
idealReceiver.viewGain();
We can see that the non-linear gain region starts at an input power of about -10 dBm, and saturates at about 0 dBm. With the current setup, the amplifier does not saturate. Even if the amplifier were to saturate, there would be limited impact considering the current setup in which a single tone CW waveform is received. The non-linearity of the receiver amplifier would impact performance if the received waveform spanned a wider bandwidth or of the received signal had variable amplitude.
Setup Geometry
The transmitter is placed at boresight of the receive array. This helper function visualizes the geometry of the setup.
helperVisualizeSetupGeometry(array,nSubarrayElements,spacing);
Simulating Received Signal
The models created above are used to simulate the expected pattern as the receive array beam is steered from -90 to 90 degrees. This simulated data is used to determine the effectiveness of the calibration routine performed in the following sections. A helper function is used to simulate the received signal. For more information on simulating signal propagation using the Phased Array System Toolbox, see Signal Simulation.
steerangle = -90:0.5:90; rxamp = helperSimulateAntennaSteering(array,idealReceiver,steerangle,radiator,nsamples,fc,sampleRate);
The plot of the simulated received amplitude with ideal receiver characteristics shows what the data collected on the real system is expected to look like.
rxampdb = mag2db(rxamp); helperPlotAntennaPattern("Simulated Signal Amplitude",{"Simulated data"},{steerangle},{rxampdb});
Antenna Calibration
Before measuring the beampattern of the antenna array, the phase shifter and gain on each individual element channel as well as the overall amplitude and phase of the two subarray channels are calibrated. Data was collected and included as part of this example to illustrate the calibration routine.
load('CalibrationData.mat','CalibrationData');
Collected Data Format
Before walking through the antenna calibration, it is worth discussing the format of the collected data and how signal amplitude is extracted from the captured data.
The IQ data captured from the receive antenna contains 1024 samples in a frame sampled at 30 MHz as previously discussed. The data is captured independently from each of the two subarrays, so each data snapshot is a 1024x2 matrix of complex numbers.
exampledata = CalibrationData.ExampleData;
The data is sampled at baseband after down-conversion. The data sample values are unitless. The sample amplitude is always considered as a portion of the maximum measurable signal voltage. It is useful to convert the data sample values to a fraction of the maximum possible measured value which is dictated by the number of bits in the ADC.
exampledatascaled = exampledata / maxdatavalue;
The signal amplitude is extracted by converting to the frequency domain and taking the amplitude of the largest magnitude tone, which is produced by the CW transmitter. The signal amplitude is always expressed in dB Full Scale (dBFS) terms, which indicates the amplitude of the signal compared to the maximum measurable signal for the device in dB.
% Convert the signal to the frequency domain fexampledata = mag2db(abs(fft(exampledatascaled)) / nsamples); % Caculate the frequency of the spectrum fspread = -sampleRate/2:sampleRate/(nsamples-1):sampleRate/2; frequency = fc + fspread; % Plot the frequency spectrum of the two channels ax = axes(figure); lines = plot(ax,frequency/1e9,fftshift(fexampledata)); lines(1).DisplayName = 'Channel 1'; lines(2).DisplayName = 'Channel 2'; title('Measured Signal, Frequency Domain'); legend(); xlabel('Frequency (GHz)'); ylabel('Amplitude (dBFS)')
% Output the measured signal amplitude amplitudes = max(fexampledata); disp(['Channel 1 Amplitude = ',sprintf('%0.1f',amplitudes(1)),' dBFS, Channel 2 Amplitude = ',sprintf('%0.1f',amplitudes(2)),' dBFS']);
Channel 1 Amplitude = -27.1 dBFS, Channel 2 Amplitude = -27.2 dBFS
For the remainder of this example, when signal amplitude is discussed, it is determined using this approach.
Antenna Pattern Data Format
After each calibration step, the antenna pattern is collected by steering the phased array beam from -90 to 90 degrees in 0.5 degree increments with the transmitter placed at 0 degrees and combining the output of the two subarray channels. So the format of the antenna pattern data for this example is a 1024 (number of samples) x 361 (number of steering angles) complex double matrix. To create the antenna pattern plot, the amplitude of the signal at each steering angle is calculated using the process described above. This amplitude extraction has already been performed on the collected data to reduce data size. This amplitude is normalized to 0 dB and plotted against the simulated antenna pattern to illustrate the effects of calibration.
% Get the uncalibrated antenna pattern amplitude uncalpattern = CalibrationData.AntennaPattern.UncalibratedPattern; % Plot the actual antenna pattern against the simulated pattern helperPlotAntennaPattern("Uncalibrated vs. Simulated Pattern",{"Simulated data","Uncalibrated pattern"},{steerangle,steerangle},{rxampdb,uncalpattern});
Analog Calibration
The first part of the calibration routine is calibrating the gain and phase shifter in the analog RF path for each antenna element in the two subarrays. The calibration values that are calculated in this section are used to configure the hardware in the phased array antenna.
Element Course Amplitude Calibration
The first step in the calibration routine is calibrating each element in the subarrays so that their amplitudes are the same. The amplitude of the signal into each antenna element is adjusted by setting the gain in the element RF path.
The signal amplitude in each element channel is measured by turning off all the other elements in the array. The gain of each antenna element is set such that all elements in a subarray have the same signal amplitude.
% Get the data collected during calibration for each subarray sub1signals = CalibrationData.AnalogAmplitudeCalibration.CourseSubarray1Data; sub2signals = CalibrationData.AnalogAmplitudeCalibration.CourseSubarray2Data; % Get the amplitude of the subarray element signals sub1amplitudes = helperCalculateAmplitude(sub1signals,maxdatavalue); sub2amplitudes = helperCalculateAmplitude(sub2signals,maxdatavalue);
For each antenna element, 20 data frames are collected for consistency. Take the mean amplitude for each antenna element.
sub1meanamp = reshape(mean(sub1amplitudes),[1 4]); sub2meanamp = reshape(mean(sub2amplitudes),[1 4]);
The amplitude calibration values for each element are then set so that the amplitude for every element in the array is equal. The gain values must be set based on the element with the minimum signal amplitude, because gain can only be decreased.
sub1gaincal = min(sub1meanamp) - sub1meanamp;
sub2gaincal = min(sub2meanamp) - sub2meanamp;
% Plot element gain calibration settings
helperPlotElementGainCalibration(sub1meanamp,sub1gaincal,sub2meanamp,sub2gaincal);
This figure shows that the gain in each element channel is set so that the amplitude of the signal is equal to the amplitude of the signal in the lowest amplitude element for each subarray.
Element Phase Calibration
The phase offset of each element channel varies. Therefore, a default phase offset must be set for each element to align the phase in each element channel.
This phase calibration is performed by turning off all elements in the subarray except for the first element and the element being tested. The phase shift of the first element is left constant at 0 degrees while the phase of the element being tested is shifted from 0 to 360 degrees.
This data is retrieved and the amplitude plotted to illustrate what the results look like.
% Get uncalibrated phased values from calibration data phasesetting = CalibrationData.AnalogPhaseCalibration.PhaseSetting; sub1signals = CalibrationData.AnalogPhaseCalibration.Subarray1Measurements; sub2signals = CalibrationData.AnalogPhaseCalibration.Subarray2Measurements; % Get the signal amplitudes from 0-360 degree phased offsets for each % element sub1amplitudes = helperCalculateAmplitude(sub1signals,maxdatavalue); sub2amplitudes = helperCalculateAmplitude(sub2signals,maxdatavalue); % Reshape into a 2d array which is Number Phases x Number Elements sub1amplitudes = reshape(sub1amplitudes,[numel(phasesetting) 3]); sub2amplitudes = reshape(sub2amplitudes,[numel(phasesetting) 3]); % Plot the data helperPlotPhaseData(phasesetting,sub1amplitudes,sub2amplitudes);
We can see in the plot of the data above that the amplitude of the combined element one and element two signal reaches a minimum at some phase shifter setting. The significance of this minimum is that this is the point at which the actual phase offset between the two channels is 180 degrees.
Based on this data, the phase calibration value is an offset added to the phase shifter setting for each element that ensures that when the phase shift is set to 180 degrees, the true phase shift between elements is actually 180 degrees.
% Calculate the calibration values and display in a table [~,sub1phaseidx] = min(sub1amplitudes); [~,sub2phaseidx] = min(sub2amplitudes); sub1calphase = phasesetting(sub1phaseidx)-180; sub2calphase = phasesetting(sub2phaseidx)-180; rowNames = ["Element 1","Element 2","Element 3","Element 4"]; varNames = ["Array 1 calibration phase (deg)","Array 2 calibration phase (deg)"]; t = table([0;sub1calphase'],[0;sub2calphase'],'RowNames',rowNames,'VariableNames',varNames); disp(t);
Array 1 calibration phase (deg) Array 2 calibration phase (deg) _______________________________ _______________________________ Element 1 0 0 Element 2 8.4375 8.4375 Element 3 -2.8125 -5.625 Element 4 -2.8125 -8.4375
Fine Element Amplitude Calibration
After the initial element phase calibration an additional element amplitude calibration is required. This is due to the impact that phase shifter changes can have on the measured amplitude. The process used for this second analog amplitude calibration is exactly the same as the initial amplitude calibration, so the steps are not repeated here.
The pattern before and after individual element calibration is plotted below.
% Plot the antenna pattern after analog calibration analogpatterndata = CalibrationData.AntennaPattern.AnalogFineAmplitudeCalPattern; helperPlotAntennaPattern("After Individual Element Calibration",{"Simulated data","Uncalibrated Pattern","Individual Element Calibration"},{steerangle,steerangle,steerangle},{rxampdb,uncalpattern,analogpatterndata})
The pattern nulls have gotten deeper after calibrating the element amplitudes and phases. However, the pattern still does not match the simulated antenna pattern due to the phase and amplitude mismatch between the two digital channels. Therefore, an additional digital calibration to align the two subarray channels is required.
Digital Calibration
After the analog calibration steps have been performed for each antenna element, calibration values for the two digital subarray channels are collected so that their amplitudes are equal and the phases align. Unlike the analog calibration, the calibration values calculated in this section are applied to the digital data streams after data collection.
Digital Amplitude Calibration
To calibrate the subarray channel amplitude, the signal in both channels is measured. The amplitude of each signal is determined. The difference is the channel amplitude calibration factor.
% Get the received signal before channel amplitude calibration digitalrxdata = CalibrationData.DigitalCalibration.MeasuredData; rxsubarray1 = digitalrxdata(:,1); rxsubarray2 = digitalrxdata(:,2); % Calculate amplitude for each subarray sub1amplitude = helperCalculateAmplitude(rxsubarray1,maxdatavalue); sub2amplitude = helperCalculateAmplitude(rxsubarray2,maxdatavalue); % The channel gain offset is the difference in amplitude between channels channel2GainOffset = sub1amplitude - sub2amplitude
channel2GainOffset = -1.2146
Digital Phase Calibration
To calibrate the subarray channel phase, the signal in channel 2 is phase shifted from 0 to 360 degrees and combined with the signal in channel 1. When the phases of the two channels have an actual offset of 0 degrees, the amplitude of the combined signals will reach a maximum. The phase offset of channel 2 that results in an actual phase offset of 0 degrees is the digital phase calibration value.
% Phase shift channel 2 from 0 to 360 and combine phasedeg = 0:360; phase = deg2rad(phasedeg); st_vec = [ones(size(phase)); exp(1i*phase)]; combinedsignal = digitalrxdata*conj(st_vec); combinedamp = helperCalculateAmplitude(combinedsignal,maxdatavalue); [maxamp, phaseidx] = max(combinedamp); channel2PhaseOffset = phasedeg(phaseidx); % Plot the digital phase offset pattern and calibration value ax = axes(figure); hold(ax,"on"); title(ax,"Digital Phase Calibration"); ylabel(ax,"dB"); xlabel(ax,"Channel 2 Phase Offset (degrees)"); plot(ax,phasedeg,combinedamp,"DisplayName","Combined Channel Power"); scatter(ax,channel2PhaseOffset,maxamp,"DisplayName","Selected Phase Offset"); legend('location','southeast');
Fully Calibrated Antenna Pattern
First, we can see how the uncalibrated pattern that was collected compares to the simulated pattern by creating a receiver with same gain and phase errors in each channel that were determined during calibration.
% Get the gains that exist in the array channels - this includes % differences from the analog and digital channels channelAbsoluteGains = [sub1meanamp,sub2meanamp-channel2GainOffset]; channelRelativeGains = channelAbsoluteGains - max(channelAbsoluteGains); channelGains = receiverGain + channelRelativeGains; % Get the phase offsets that exist in the array channels - this includes % offsets from the analog and digital channels channelPhaseOffsets = [0,sub1calphase,[0,sub2calphase]-channel2PhaseOffset]; % Set up the receiver with the gains and phase offsets determined during % the calibration uncalibratedReceiver = phased.Receiver(GainMethod="Cubic polynomial",Gain=channelGains,OIP3=receiverOip3,NoiseMethod="Noise figure",NoiseFigure=receiverNf,SampleRate=sampleRate,PhaseOffset=channelPhaseOffsets); % Get the simulated antenna pattern with the known phase and gain % errors uncalsim = helperSimulateAntennaSteering(array,uncalibratedReceiver,steerangle,radiator,nsamples,fc,sampleRate); uncalsimdb = mag2db(uncalsim); helperPlotAntennaPattern("Uncalibrated Pattern",{"Simulated uncalibrated data","Collected uncalibrated data"},{steerangle,steerangle},{uncalsimdb,uncalpattern});
Finally, the pattern of the fully calibrated pattern is plotted compared to the simulated pattern.
% Plot the antenna pattern after digital channel amplitude calibration calpattern = CalibrationData.AntennaPattern.FullCalibration; helperPlotAntennaPattern("Fully Calibrated Antenna",{"Simulated data","Full Calibration"},{steerangle,steerangle},{rxampdb,calpattern})
With this final calibration step, the measured antenna pattern closely matches the simulated pattern.
Real Data vs. Simulation
Once calibrated, real data was collected for a few different situations that were also simulated using the Phased Array System Toolbox. This section illustrates that although the simulation predictions are not perfect, they closely match the real data under a number of different operating conditions.
Sparse Array Grating Lobes
Phased arrays can be configured to have spacing between elements of greater than 1/2 wavelength to reduce hardware costs while still meeting functional requirements for the specified task. These sparse arrays will have grating lobes in the array response. The Phased Array System Toolbox can be used to model the array response of any array geometry, including sparse arrays.
In this section, we implemented array sparsity by disabling certain elements in the hardware antenna and capturing beam pattern data. This collected data is compared against simulated data to illustrate the usefulness of the Phased Array System Toolbox in simulating any type of array geometry.
load('SparseArray.mat','SparseArray');
The impairment data contains information about the element that was disabled and the resulting beam pattern that was collected. For the sake of this example, the same antenna element was disabled in both subarrays.
A helper function is used to run a simulation disabling the same elements that were disabled during data collection and comparing the simulated data to the collected data. This data shows that simulation can be used to effectively model the grating lobes introduced by a sparse array.
helperPlotSparseArray(SparseArray,array,idealReceiver,radiator,nsamples,fc,sampleRate);
The simulated pattern closely matches the measured pattern when disabling the elements within the subarray.
Tapering
The sidelobe characteristics of a phased array can be altered by attenuating the signal into certain elements in the array. This is a technique known as tapering. Tapering was implemented in the phased array hardware and the measured results were compared to simulation. A Taylor window taper was used in the data collection and simulation. For more information on antenna tapering, see Tapering, Thinning and Arrays with Different Sensor Patterns.
load('AntennaTaperData.mat','AntennaTaperData');
A helper function is used to parse the data and compare with simulated results.
helperPlotAntennaTaper(AntennaTaperData,array,idealReceiver,radiator,nsamples,fc,sampleRate);
In this case, the results match somewhat closely but the sidelobes are not reduced to the desired extent in the actual hardware implementation.
Pattern Nulling
Nulling is a technique that can be used to avoid the impact of inference on array performance. By inserting a null into the beampattern, the received power from known interferers can be reduced. In this case a simple null cancellation technique is used to null a certain portion of the array pattern. For more information on nulling techniques, see Array Pattern Synthesis Part I: Nulling, Windowing, and Thinning.
load('NullSteeringData.mat','NullSteeringData');
A helper function is used to display the collected data with and without a null in the pattern.
helperNullSteering(NullSteeringData,array,idealReceiver,radiator,nsamples,fc,sampleRate);
The collected null pattern closely matches the shape of the simulated null pattern.
Summary
In this example we collected data using a real phased array antenna. We walk through a simple calibration routine and show the impact that calibration has on the antenna pattern by comparing the data measured using an uncalibrated and calibrated antenna.
Additionally, we compare the data collected with certain elements disabled, the array pattern tapered, and a null canceller applied to the beam pattern. The Phase Array System Toolbox is used to simulate these various data collection scenarios and the agreement between real world data and simulated data is demonstrated.
Although the simulated data closely matches the collected data in all these scenarios, the fidelity of the model could always be further improved for better results. For example, some areas that could be explored for further model improvements are:
Add the antenna element patterns for the transmit and receive antennas in place of the isotropic assumption.
Add environmental noise
Add impairments such as phase noise and mutual coupling
The level of fidelity required is use case dependent, but even a simple model like the one used in this example provides results that appear very similar to data collected using a real world antenna.
References
[1] Phased Array Radar Workshop. Analog Devices, June 20, 2022, https://www.analog.com/media/en/other/tradeshows/ims2022-adi-phasedarrayworkshop.pdf.
[2] Analog Device Inc. ADL 8107 Low Noise Amplifier, October 16, 2023, https://www.mouser.com/new/analog-devices/adi-adl8107-amplifiers/?gclid=CjwKCAjwvrOpBhBdEiwAR58-3Ds7tNIbmYqhkB-KLX9Ffczui8g1PY0TU5NY16gfMl7Qn0pZN3cwHhoCkC8QAvD_BwE.
Helper Functions
The following helper functions create the visualizations for this example.
function amplitude = helperCalculateAmplitude(data,maxvalue) % Get the signal amplitude % Scale data datascaled = data / maxvalue; [nsamples,~] = size(data); % Convert the signal to the frequency domain fexampledata = mag2db(abs(fft(datascaled)) / nsamples); % Amplitude is the largest frequency value amplitude = max(fexampledata); end function helperPlotAntennaPattern(plottitle,name,steerangle,rxamp,ax) % Plot an antenna pattern % Set up the figure if nargin < 5 ax = axes(figure); end hold(ax,"on"); title(ax,plottitle); xlabel(ax,"Steering Angle (degrees)"); ylabel(ax,"Normalized Amplitude (dB)"); % Plot each antenna pattern that was passed in numfigures = numel(name); for iFig = 1:numfigures curname = name{iFig}; cursteerangle = steerangle{iFig}; currxamp = rxamp{iFig}; plotSinglePattern(ax,curname,cursteerangle,currxamp); end legend(ax,"location","southeast"); function plotSinglePattern(ax,name,angle,amp) normData = amp - max(amp); plot(ax,angle,normData,"DisplayName",name); end end function helperVisualizeSetupGeometry(array,subarrayElements,spacing) % Visualize the hardware setup for this example. % Setup figure f = figure; a = axes(f,"XTick",[],"YTick",[]); a.XAxis.Visible = false; a.YAxis.Visible = false; title(a,"Setup Geometry"); hold(a,"on"); % Get the position of the arrays rxArrayPositions = array.getElementPosition(); subarray1Position = rxArrayPositions(1:2,1:subarrayElements); subarray2Position = rxArrayPositions(1:2,subarrayElements+1:end); txPosition = [spacing*5;0]; % Plot the arrays scatter(a,subarray1Position(1,:),subarray1Position(2,:),40,"filled","o","MarkerFaceColor",[0,0.44,0.74],"DisplayName","Rx Subarray 1"); scatter(a,subarray2Position(1,:),subarray2Position(2,:),40,"filled","o","MarkerFaceColor",[0.85,0.32,0.10],"DisplayName","Rx Subarray 2"); scatter(a,txPosition(1),txPosition(2),40,"yellow","filled","o","MarkerFaceColor",[0.93,0.69,0.12],"DisplayName","Transmitter"); legend(a); xlim(a,[-spacing*2 txPosition(1)]); ylim(a,[-0.1 0.1]); hold(a,"off"); end function [array1gaincal,array2gaincal] = helperPlotElementGainCalibration(sub1meanamp,sub1gaincal,sub2meanamp,sub2gaincal) % Calculate and visualize the element-wise amplitude calibration for % both subarrays. % Setup figure figure; tiledlayout(1,2); a = nexttile(); % Calculate and plot the gain calibration for subarray 1 array1gaincal = helperElementSubarrayGainCalibration(a,'Subarray 1',sub1meanamp, sub1gaincal); a = nexttile(); % Calculate and plot the gain calibration for subarray 2 array2gaincal = helperElementSubarrayGainCalibration(a, 'Subarray 2', sub2meanamp, sub2gaincal); end function arraygaincal = helperElementSubarrayGainCalibration(ax,name,amplitudes,arraygaincal) % Calculate and visualize the element-wise amplitude calibration for % one subarray. hold(ax,"on"); % Normalize amplitude for each element in the array dbNormAmplitudes = amplitudes - max(amplitudes); % Plot normalized amplitudes and gain adjustments b = bar(ax,[dbNormAmplitudes',arraygaincal'],'stacked'); b(1).DisplayName = "Initial Normalized Amplitude"; b(2).DisplayName = "Gain Adjustment"; % Plot a line showing the final amplitude of all elements plot(ax,[0,5],[min(dbNormAmplitudes),min(dbNormAmplitudes)],"DisplayName","Final Element Amplitude","LineWidth",2,"Color","k") xlabel('Antenna Element') ylabel('dB'); title([name ' - Gain Calibration']) legend('Location','southoutside') end function helperPlotPhaseData(phasesetting,sub1amplitudes,sub2amplitudes) % Visualize the element-wise phase calibration data for the entire % array. figure; tiledlayout(1,2); nexttile(); helperPlotPhaseSubarrayData("Subarray 1",phasesetting,sub1amplitudes); nexttile(); helperPlotPhaseSubarrayData("Subarray 2",phasesetting,sub2amplitudes); end function helperPlotPhaseSubarrayData(name, phasesetting, phaseOffsetAmplDb) % Visualize the element-wise phase calibration data for a subarray. lines = plot(phasesetting, phaseOffsetAmplDb); lines(1).DisplayName = "Element 2"; lines(2).DisplayName = "Element 3"; lines(3).DisplayName = "Element 4"; title([name,' - Phase Calibration Data']) ylabel("Amplitude (dB) Element X + Element 1") xlabel("Phase Shift Setting (degrees)") legend("Location","southoutside") end function helperPlotSparseArray(SparseArrayData,array,receiver,radiator,nsamples,fctransmit,sampleRate) % Plot the simulated impairment data as well as the collected % impairment data. [~,numdisabledelements] = size(SparseArrayData); % Setup a tiled figure f = figure; tiledlayout(f,2,2); % Loop through each disabled element. Compare simulation to % measurement. for iImpair = 1:numdisabledelements % Get the element to disable. disabledElement = SparseArrayData(iImpair).Element; % Get the steer angles to simulate steerangle = SparseArrayData(iImpair).SteerAngle; % Generate the antenna pattern by inserting zeros into disabled % elements for analog weights. analogweights = ones(4,2); analogweights(iImpair,:) = 0; rxamp = helperSimulateAntennaSteering(array,receiver,steerangle,radiator,nsamples,fctransmit,sampleRate,analogweights); rxampsim = mag2db(rxamp); % Get the collected antenna pattern rxampcollected = SparseArrayData(iImpair).Pattern; % Plot the figure ax = nexttile(); helperPlotAntennaPattern(['Element ',num2str(disabledElement),' Disabled'],{"Simulated","Collected"},{steerangle,steerangle},{rxampsim,rxampcollected},ax) end end function helperPlotAntennaTaper(TaperData,array,receiver,radiator,nsamples,fctransmit,sampleRate) % Plot the simulated taper pattern as well as the collected % impairment data. % Get steer angles used steerangle = TaperData.SteerAngles; % Get the sidelobe level specified sidelobelevel = TaperData.SidelobeLevel; % Get the collected data after tapering rxamptaper = TaperData.TaperedPattern; % Simulate the antenna pattern using the taper applied during data % collection analogweights = TaperData.Taper; rxdatasim = helperSimulateAntennaSteering(array,receiver,steerangle,radiator,nsamples,fctransmit,sampleRate,analogweights); rxampsim = mag2db(rxdatasim); % Plot collected data with and without taper as well as simulated taper ax = axes(figure); helperPlotAntennaPattern("Antenna Tapering",{"Simulated Taper","Collected With Taper"},{steerangle,steerangle},{rxampsim,rxamptaper},ax) plot(ax,[steerangle(1),steerangle(end)],[sidelobelevel,sidelobelevel],"DisplayName","Specified Sidelobe Level","LineStyle","--"); end function helperNullSteering(NullSteeringData,array,receiver,radiator,nsamples,fctransmit,sampleRate) % Plot the simulated and collected null steering data % Get steer angles used steerangle = NullSteeringData.SteerAngles; % Get the null angle specified nullangle = NullSteeringData.NullAngle; % Get the collected data after inserting a null rxampnull = NullSteeringData.PatternAfterNull; % Simulate the antenna pattern with nulling rxdatasim = helperSimulateAntennaSteering(array,receiver,steerangle,radiator,nsamples,fctransmit,sampleRate,ones(4,2),ones(2,1),nullangle); rxampsim = mag2db(rxdatasim); % Plot collected data with and without null as well as simulated null ax = axes(figure); helperPlotAntennaPattern("Pattern Nulling",{"Simulated Null Pattern","Collected With Null"},{steerangle,steerangle},{rxampsim,rxampnull},ax) % plot the null location rxampsimnorm = rxampsim-max(rxampsim); collectampnorm = rxampnull-max(rxampnull); rxampsimnorm(rxampsimnorm == -Inf) = []; collectampnorm(collectampnorm == -Inf) = []; minnorm = min([rxampsimnorm,collectampnorm]); plot(ax,[nullangle,nullangle],[minnorm,0],"DisplayName","Null Direction","LineStyle",":","LineWidth",3); end function [rxamp,rxphase] = helperSimulateAntennaSteering(array,receiver,steerangle,radiator,nsamples,fctransmit,sampleRate,analogweight,digitalweight,nullangle) % Simulate antenna steering. arguments array receiver steerangle radiator nsamples fctransmit sampleRate analogweight (4,2) double = ones(4,2) digitalweight (2,1) double = ones(2,1) nullangle = [] end % Setup collector collector = phased.Collector("Sensor",array,"OperatingFrequency",fctransmit,"WeightsInputPort",true); % Single tone CW signal is used signal = 1e-2*ones(nsamples,1); % Set up a channel for radiating signal channel = phased.FreeSpace("OperatingFrequency",fctransmit,"SampleRate",sampleRate); % Setup geometry rxpos = [0;0;0]; rxvel = [0;0;0]; txpos = [0;10;0]; txvel = [0;0;0]; [~,ang] = rangeangle(rxpos,txpos); % Radiate signal sigtx = radiator(signal,ang); % Propagate signal sigtx = channel(sigtx,txpos,rxpos,txvel,rxvel); % Create a beamformer psbits = 8; directions = [steerangle;zeros(1,length(steerangle))]; beamformer = phased.PhaseShiftBeamformer("SensorArray",array,"OperatingFrequency",fctransmit,"Direction",directions,"NumPhaseShifterBits",psbits); % Create the array weights weights = [analogweight(:,1)*digitalweight(1);analogweight(:,2)*digitalweight(2)]; % Insert a null if a null angle was passed in if ~isempty(nullangle) % Null the replicated array pos = array.getElementPosition() / freq2wavelen(fctransmit); null = [nullangle;0]; nullweight = steervec(pos,null,psbits); weights = getNullSteer(weights,nullweight); end % Collect the signal sigcollect = collector(sigtx,[0;0],weights); % Pass through the receiver sigreceive = receiver(sigcollect); % Beamform the signal sigdigital = beamformer(sigreceive); % Get the received signal amplitude and phase rxphase = mean(rad2deg(angle(sigdigital)),1); rxamp = mean(abs(sigdigital),1); function nullsteer = getNullSteer(steerweight,nullweight) rn = nullweight'*steerweight/(nullweight'*nullweight); nullsteer = steerweight-nullweight*rn; end end