SDR_al/Matlab/calibrationRoutine.m

353 lines
13 KiB
Matlab
Raw Permalink Normal View History

2023-07-24 15:14:00 +00:00
function CalibrationData = calibrationRoutine(fc_hb100)
% Setup:
%
% Place the HB100 in front of the Phaser - 0 degree azimuth. It should be
% sufficiently far from the antenna so that the wavefront is approximately
% planar.
%
% Notes:
%
% The amplitude and phase of all 8 analog channels as well as the two
% digital channels must be calibrated so that the amplitudes in each
% channel are the same and the phase shifts align as expected during
% steering.
%
% The order of the calibration steps is described below.
%
% 1. Course Analog Amplitude Calibration: The first calibration step is an
% initial amplitude calibration so that the amplitude in each of the 4
% elements in each of the 2 subarrays are the same. This is done by
% independently measuring the signal amplitude in each channel when the gain
% is set to maximum.The gain is set so that all of the channel signal
% amplitudes match the lowest signal amplitude channel.
%
% 2. Analog Phase Calibration: The second calibration step is to account for
% the different phase shift in each analog channel. For each subarray,
% select a reference channel. Then measure the phase of other channels
% within the subarray with respect to the reference channel. The search is
% brute force, by sweeping all the possible phases on the chip.
%
% 3. Fine Analog Amplitude Calibration: Changing the default phase shift in
% each analog channel in step (2) causes the amplitude of the signal in
% each channel to change. Therefore, the amplitude calibration is performed
% again.
%
% 4. Digital Amplitude Calibration: Once the analog channels have been
% calibrated, the difference in amplitude between the two digital channels
% is measured. Whenever the digital channel signals are combined, an
% amplitude adjustment is applied based on this different in amplitude.
%
% 5. Digital Phase Calibration: The signal phase into each of the two
% digital channels will not necessarily align. The phase difference is
% measured by shifting the phase of channel 2 until the amplitude of the
% combined signal is maximized. This phase shift is applied when the two
% digital signals are combined.
%% Setup
% Setup antenna and model
[rx,bf,~] = setupAntenna(fc_hb100);
% Setup calibration data storage object
CalibrationData = CalibrationDataFormat();
% Setup the inital analog and digital antenna weights. These are the values
% that get adjusted during calibration. The amplitude and phase of the weights
% represent the gain and phase adjustments that will be made on each channel.
% Initially there is no phase shift or amplitude adjustment.
analogWeights = ones(4,2);
digitalWeights = ones(2,1);
% Store uncalibrated weights for later analysis
CalibrationData.CalibrationWeights.UncalibratedWeights.AnalogWeights = analogWeights;
CalibrationData.CalibrationWeights.UncalibratedWeights.DigitalWeights = digitalWeights;
%% Course Amplitude Calibration
% The amplitude in each analog channel is measured when the gain is
% set to the maximum level. Gain in each channel is adjusted to account
% for differences.
% Set the gain of all 8 channels to maximum.
bf.RxGain(:) = 127;
% Setup data variables
nCapture = 20;
rx1data = zeros(1024,nCapture,4);
rx2data = zeros(1024,nCapture,4);
for nCh = 1:4
% Turn off all the channels.
bf.RxPowerDown(:) = 1;
% Turn on channels under test (subarray 1 and subarray 2)
bf.RxPowerDown(nCh) = 0;
bf.RxPowerDown(nCh+4) = 0;
bf.LatchRxSettings();
% Capture multiple snapshots
for ncapture = 1 : nCapture
rx();
receivedSig = rx();
rx1data(:,ncapture,nCh) = receivedSig(:,2);
rx2data(:,ncapture,nCh) = receivedSig(:,1);
end
end
% Turn all channels back on
bf.RxPowerDown(:) = 0;
% Calculate the average amplitude for each element in each subarray
avgamp1 = mean(max(abs(fft(rx1data))));
avgamp2 = mean(max(abs(fft(rx2data))));
% Reshape into a vector
avgamp1 = reshape(avgamp1,[1 4]);
avgamp2 = reshape(avgamp2,[1 4]);
% Scale each amplitude value to the minimum measured amplitude value
sub1gain = min(avgamp1) ./ avgamp1;
sub2gain = min(avgamp2) ./ avgamp2;
% Plot the analog amplitude calibration values
helperPlotElementGainCalibration(avgamp1,sub1gain,avgamp2,sub2gain);
% Update analog weights with array gain calibration values
analogWeights = analogWeights .* [sub1gain',sub2gain'];
% Store the calibration data for later analysis
CalibrationData.AnalogAmplitudeCalibration.CourseSubarray1Data = rx1data;
CalibrationData.AnalogAmplitudeCalibration.CourseSubarray2Data = rx2data;
CalibrationData.CalibrationWeights.AnalogCourseAmplitudeWeights.AnalogWeights = analogWeights;
%% Calibrate phase
% Place the emitter in front of the phased array.
% Turn on two adjacent channel. On the first channel put 0 phase, and on
% the adjacent channel sweep the phase shift setting from 0 to 360. Minimum
% signal strength occurs when the phase offset between channels is 180
% degrees. The calibration value is set such that the phase shift is
% actually 180 degrees when shifter is set to 180 degrees.
% Setup phase steps to test and variables to hold data
PhaseResolutionBits = 7;
phase_step_size = 360 / (2 ^ PhaseResolutionBits);
channel2phase = double(0:phase_step_size:360);
rawdata1 = zeros(rx.SamplesPerFrame,numel(channel2phase),3);
rawdata2 = zeros(rx.SamplesPerFrame,numel(channel2phase),3);
% Set the reference channel and channels under test
referencechannel = 1;
testchannels = 2:4;
% set the receiver gain based on the amplitude calibration. The gain codes
% are set based on data that was previously captured to determine gain code
% correspondance to actual gain.
bf.RxGain = helperGainCodes(analogWeights);
% Loop through each channel under test
for nChannel = testchannels
% Set all phase shifts to 0
bf.RxPhase(:) = 0;
% Power down all elements
bf.RxPowerDown(:) = 1;
% Turn on element 1 and element under test subarray 1
bf.RxPowerDown(referencechannel) = 0;
bf.RxPowerDown(nChannel) = 0;
% Turn on element 1 and element under test subarray 2
bf.RxPowerDown(referencechannel+4) = 0;
bf.RxPowerDown(nChannel+4) = 0;
bf.LatchRxSettings();
% Loop through each phase offset, set element under test phase shifter,
% capture data.
for ii = 1 : numel(channel2phase)
% Set phase for subarray 1 and 2 element
bf.RxPhase(nChannel) = channel2phase(ii);
bf.RxPhase(nChannel+4) = channel2phase(ii);
bf.LatchRxSettings();
% Capture and store data
rx();
receivedSig_HW = rx();
rawdata1(:,ii,nChannel-1) = receivedSig_HW(:,2);
rawdata2(:,ii,nChannel-1) = receivedSig_HW(:,1);
end
end
% Get signal amplitude for both subarrays
amp1 = max(abs(fft(rawdata1)),[],1);
amp2 = max(abs(fft(rawdata2)),[],1);
% Reshape into a 2d array which is Number Phases x Number Elements
sub1amplitudes = reshape(amp1,[numel(channel2phase) 3]);
sub2amplitudes = reshape(amp2,[numel(channel2phase) 3]);
% Get location of min amplitude
[~,phase1Idx] = min(sub1amplitudes,[],1);
[~,phase2Idx] = min(sub2amplitudes,[],1);
% Get calibration phase by substracting 180 from the minimum phase location
cal1phase = [0;wrapTo360(channel2phase(phase1Idx)-180)'];
cal2phase = [0;wrapTo360(channel2phase(phase2Idx)-180)'];
% Plot the analog phase shift information
helperPlotAnalogPhaseCalibration(channel2phase,sub1amplitudes,sub2amplitudes);
% Convert calibration phase to a phase shift for the element weights, and
% update analog weights
phaseSteer = exp(deg2rad([cal1phase,cal2phase])*1i);
analogWeights = analogWeights .* phaseSteer;
% Store recorded data for later analysis
CalibrationData.AnalogPhaseCalibration.PhaseSetting = channel2phase;
CalibrationData.AnalogPhaseCalibration.Subarray1Measurements = rawdata1;
CalibrationData.AnalogPhaseCalibration.Subarray2Measurements = rawdata2;
CalibrationData.CalibrationWeights.AnalogPhaseWeights.AnalogWeights = analogWeights;
%% Fine Grain Calibration Data
% The amplitude in each analog channel is measured when the gain is
% set to the maximum level with the new default phase shifts applied.
% Gain in each channel is adjusted to account for differences. This is the
% exact same process that was used for the analog course amplitude
% calibration, although now the phase shifter values are set to the default
% determined in the previous section.
% Set the gain of all 8 channels to maximum.
bf.RxGain(:) = 127;
% Set the phase shifts based on the new analog weights
phaseshifts = wrapTo360(rad2deg(angle(analogWeights)));
bf.RxPhase(:) = [phaseshifts(:,1)',phaseshifts(:,2)'];
% Setup data variables
nCapture = 20;
rx1data = zeros(1024,nCapture,4);
rx2data = zeros(1024,nCapture,4);
for nCh = 1:4
% Turn off all the channels.
bf.RxPowerDown(:) = 1;
% Turn on channels under test (subarray 1 and subarray 2)
bf.RxPowerDown(nCh) = 0;
bf.RxPowerDown(nCh+4) = 0;
bf.LatchRxSettings();
% Capture multiple snapshots
for ncapture = 1 : nCapture
rx();
receivedSig = rx();
rx1data(:,ncapture,nCh) = receivedSig(:,2);
rx2data(:,ncapture,nCh) = receivedSig(:,1);
end
end
% Turn all channels back on
bf.RxPowerDown(:) = 0;
% Calculate the average amplitude for each element in each subarray
avgamp1 = mean(max(abs(fft(rx1data))));
avgamp2 = mean(max(abs(fft(rx2data))));
% Reshape into a vector
avgamp1 = reshape(avgamp1,[1 4]);
avgamp2 = reshape(avgamp2,[1 4]);
% Scale each amplitude value to the minimum measured amplitude value
sub1gain = min(avgamp1) ./ avgamp1;
sub2gain = min(avgamp2) ./ avgamp2;
% Normalize the weights back to amplitude 1
analogWeights = analogWeights ./ abs(analogWeights);
% Update analog weights with array gain calibration values
analogWeights = analogWeights .* [sub1gain',sub2gain'];
% Store the calibration data for later analysis
CalibrationData.AnalogAmplitudeCalibration.FineSubarray1Data = rx1data;
CalibrationData.AnalogAmplitudeCalibration.FineSubarray2Data = rx2data;
CalibrationData.CalibrationWeights.AnalogFineAmplitudeWeights.AnalogWeights = analogWeights;
%% Calibrate the two digital channels on Pluto
% After the analog channels have been calibrated, collect data with
% analog calibration value settings and calibrate the digital channels for
% amplitude and phase.
% Collect data with analog weight settings
analogcaldata = helperSteerAnalog(bf,rx,analogWeights);
% Store calibration data for later analysis
CalibrationData.DigitalCalibration.MeasuredData = analogcaldata;
% Calculate the channel calibration gain by normalizing the channel
% amplitudes to the max channel amplitude
channelamplitude = max(abs(fft(analogcaldata)));
gain = max(channelamplitude) ./ channelamplitude;
digitalWeights = digitalWeights .* gain';
% Save the weights for later analysis
CalibrationData.CalibrationWeights.DigitalAmplitudeWeights.AnalogWeights = analogWeights;
CalibrationData.CalibrationWeights.DigitalAmplitudeWeights.DigitalWeights = digitalWeights;
% Calculate the channel calibration phase by steering channel 2 from 0 to
% 360 and finding the maximum combined channel amplitude
channel2phase = deg2rad(0:360);
st_vec = [ones(size(channel2phase)); exp(1i*channel2phase)];
sig = analogcaldata*conj(st_vec);
sigfft = fft(sig);
combinedamplitude = max(abs(sigfft));
[~, phaseIdx] = max(combinedamplitude);
% 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 (deg)");
plot(ax,channel2phase,combinedamplitude,"DisplayName","Combined Channel Power");
scatter(ax,channel2phase(phaseIdx),combinedamplitude(phaseIdx),"DisplayName","Selected Phase Offset");
legend('location','southeast');
% Set the new digital weights
defaultsteervec = st_vec(:,phaseIdx);
digitalWeights = digitalWeights .* defaultsteervec;
% Save the final calibration weights
CalibrationData.CalibrationWeights.FinalCalibrationWeights.AnalogWeights = analogWeights;
CalibrationData.CalibrationWeights.FinalCalibrationWeights.DigitalWeights = digitalWeights;
%% Release Hardware
bf.release(); delete(bf);
rx.release(); delete(rx);
%% Plot final data: uncalibrated data vs. calibrated data vs. simulated data
% Use a custom class called AntennaInteractor to help steer the antenna
initialcalvalues = CalibrationData.CalibrationWeights.UncalibratedWeights;
antennaInteractor = AntennaInteractor(fc_hb100,initialcalvalues);
steerangles = -90:0.5:90;
% Capture the pattern with the initial calibration values
[initialpattern,~] = antennaInteractor.capturePattern(steerangles);
initialamp = mag2db(helperGetAmplitude(initialpattern));
% Capture the pattern with the final calibration values
finalcalvalues = CalibrationData.CalibrationWeights.FinalCalibrationWeights;
antennaInteractor.updateCalibration(finalcalvalues);
[finalpattern,~] = antennaInteractor.capturePattern(steerangles);
finalamp = mag2db(helperGetAmplitude(finalpattern));
% Simulate the expected antenna pattern
rxpos = [0;0;0]; % phaser at 0
txpos = [0;10;0]; % transmitter 10 meters away at 0 boresight
simpattern = mag2db(helperSimulateAntennaSteering(fc_hb100,rxpos,txpos,steerangles));
% Plot data
patternax = axes(figure); hold(patternax,"on"); legend(patternax);
xlabel(patternax,"Steer Angle (deg)"); ylabel(patternax,"Amplitude (dB)"); title(patternax,"Calibration Effect");
plot(patternax,steerangles,initialamp - max(initialamp),'DisplayName','Uncalibrated Array Factor');
plot(patternax,steerangles,finalamp - max(finalamp),'DisplayName','Calibrated Array Factor');
plot(patternax,steerangles,simpattern - max(simpattern),'DisplayName','Simulated Array Factor');
end