🎯 Objective

By the end of this lab, students will be able to:

  • Explain how a Class-D amplifier converts a low-power sinusoidal reference signal into a high-power AC output using modulation, a half-bridge power stage, and an output filter.
  • Describe the role of the modulator in generating PWM signals and how the duty ratio encodes the input signal.
  • Measure and interpret time-domain waveforms and frequency-domain spectra (FFT) at different stages of the system, including the reference signal, switching signals, switch node, and filtered output.
  • Identify key spectral components such as the DC component, fundamental frequency, switching frequency, and harmonics, and explain how these evolve through the system.
  • Evaluate how changes in reference amplitude and DC bus voltage affect modulation depth, output amplitude, and delivered power.
  • Analyze the effect of output filter capacitance on waveform reconstruction, switching ripple attenuation, and overall signal quality.
  • Relate electrical measurements to perceptual outcomes by explaining how filtering impacts audible sound quality when driving a speaker.

📚 Prerequisite

  • Experiment A1, A2, and A3
  • Experiment E1
  • Basic understanding of frequency response characteristics, basic idea of Bode plots
  • Being able to do Fast Fourier Transform (FFT) using MATLAB (example code provided) or on the scope.

🧠 Theory

In the previous two experiments, we examined how a loudspeaker, together with an output filter network, responds to sinusoidal excitation provided by a signal generator. These experiments helped us understand the frequency-dependent impedance of the speaker and the role of the filter in shaping the voltage and current delivered to the load.

If you observe carefully, the signal generator in the earlier experiments was set to a nominal output of 20 V, but its output collapsed to the millivolt range when connected to the speaker, delivering only milliamps of current. This is because signal generators are designed for signal-level operation, not for power delivery. This observation naturally raises an important question: how can we generate a sinusoidal signal that not only replicates the waveform of a signal generator but also delivers sufficient power to drive the speaker?

To overcome this limitation of the signal generator, we require a power stage capable of generating a controlled ac voltage while supplying significant current to the load. In this experiment, we will use a half-bridge circuit to synthesize an ac waveform from a dc source, as shown in Fig. 1. Instead of directly generating a sinusoidal voltage, the half-bridge produces a high-frequency switched waveform whose average value follows a sinusoidal reference. The output filter then removes unwanted frequency components, resulting in a relatively smooth sinusoidal voltage across the speaker (depending on the filter design). This forms the foundation of a power electronic audio amplifier, where switching circuits, rather than linear amplification, are used to efficiently generate high-power ac signals from a dc supply.

Fig. 1: In this experiment, we will focus on the half-bridge stage that will act as the power amplifier.

To understand this process experimentally, we begin with a low-power sinusoidal reference signal and track how it evolves through each stage of the system. To keep our life simple at the beginning (and less painful to our ears), we will use resistors as loads. The reference is first processed by the modulator to generate switching signals, which encode the low-frequency information within a high-frequency switching waveform. This signal is then applied to the half-bridge power stage (after dead time circuit and gate driver), producing a large-amplitude switched voltage at the output node (Vout). Although this waveform contains the desired low-frequency component, it is dominated by switching harmonics. By passing the output through the filter network studied in previous experiments, the high-frequency components are attenuated, dc is eliminated, and a sinusoidal voltage is recovered across the load.

Importantly, the amplitude of the output voltage can be controlled in two ways: by adjusting the dc supply voltage, which sets the available power level, and by varying the amplitude of the reference signal, which determines the modulation depth. This demonstrates a key principle of power electronics: the separation of signal synthesis and power delivery, enabling efficient generation of high-power ac signals from a dc source.

In the final step, the resistive load is replaced with a loudspeaker to observe the system behavior under a real, frequency-dependent load. The output of the filtered half-bridge is now used to drive the speaker, allowing the generated waveform to be heard as an audible tone. The filter capacitor value is then varied to study its effect on the output voltage, output current, and perceived sound quality. As the capacitance changes, the filter cutoff frequency shifts, altering how effectively the high-frequency switching components are attenuated. Students will observe that insufficient filtering results in audible distortion or noise due to the presence of switching harmonics, while excessive filtering may attenuate the desired signal. This demonstrates the critical role of filter design in ensuring both electrical performance and audio quality.

🧰 Required Components

The components needed in this lab are:

  • Blue Board
  • Red Board
  • Black Board
  • Oscilloscope
  • Signal Generator
  • Speaker (Option: Soberton WSP-5090-4 (4 Ohm, 10 W speaker) or equivalent).
  • Multimeter
  • Current probe (if interested in measuring load current)

🎥 Overview Video

This video gives you a quick glimpse of what you can expect from this lab.

🛡️ Safety

Watch out for potential safety issues.

  1. Confirm probe grounds are properly connected for each measurement.
  2. Don't power the blue board using both the USB-C cable and the power adapter.
  3. Always disconnect the power once you are done with the experiment.

⚠ Common Mistakes

  1. The gate of a MOSFET is directly connected to a microcontroller digital pin to perform switching actions without a gate driver.
  2. Incorrect grounding between comparator output, vref source, and carrier source. All of the signals are referenced relative to gnd of the blue board.
  3. Scope probe not set to dc-coupling.
  4. The reference of the scope channels are not properly set to zero at the start of the experiment. This will lead to incorrect reading of the signals.
  5. Forgetting to record Capacitances (C).

Code

%% Experiment E3 FFT Code
% -------------------------------------------------------------------------
% This script:
% 1. Lets the user select a CSV file
% 2. Searches the file to find the actual header row containing TIME
% 3. Reads the data table even if metadata exists before the header
% 4. Lets the user choose which channel to analyze
% 5. Computes a Hann-windowed FFT
% 6. Plots the spectrum in dB
% 7. Auto-labels dominant audio peaks, switching frequency, and sidebands
% 8. Generates summary tables
%
% Typical CSV format example:
%   TIME, CH1, CH2, CH3
%   -2.000000e+00, 1.88, -1.4, -0.556
%   ...
% -------------------------------------------------------------------------

clear; clc; close all;

%% ---------------- USER SETTINGS ----------------
fsw_target = 100e3;           % Expected switching frequency in Hz
audio_band = [20, 20e3];      % Audio band for dominant peaks
full_plot_max = 150e3;        % Max frequency shown in full-spectrum plot
num_audio_peaks = 10;         % Number of dominant audio peaks to report
num_labels_audio = 5;         % Number of audio peaks to label on plot
peak_threshold_ratio = 0.02;  % Relative threshold for peak detection
switch_search_bw = 10e3;      % Search window around switching frequency
sideband_tol = 300;           % Tolerance for sideband matching (Hz)
remove_mean_for_ac_fft = true;% Remove DC before FFT
db_floor = -120;              % Lower dB limit for display
epsilon = 1e-12;              % Avoid log(0)
normalize_spectrum = false;    % true -> strongest FFT component = 0 dB

%% ---------------- SELECT FILE ----------------
[fileName, filePath] = uigetfile({'*.csv', 'CSV Files (*.csv)'}, ...
    'Select the CSV file containing scope data');

if isequal(fileName, 0)
    error('No file selected. Script terminated.');
end

fullFileName = fullfile(filePath, fileName);

%% ---------------- FIND HEADER ROW ----------------
fid = fopen(fullFileName, 'r');
if fid == -1
    error('Could not open file: %s', fullFileName);
end

headerRow = -1;
lineCount = 0;
headerLineText = '';

while ~feof(fid)
    thisLine = fgetl(fid);
    lineCount = lineCount + 1;

    if ischar(thisLine)
        if contains(upper(thisLine), 'TIME')
            headerRow = lineCount;
            headerLineText = thisLine;
            break;
        end
    end
end
fclose(fid);

if headerRow == -1
    error('Could not find a header row containing TIME in the file.');
end

fprintf('Detected header row at line %d:\n%s\n\n', headerRow, headerLineText);

%% ---------------- READ TABLE FROM HEADER ROW ----------------
opts = detectImportOptions(fullFileName, ...
    'NumHeaderLines', headerRow - 1, ...
    'VariableNamingRule', 'preserve');

opts.DataLines = [headerRow + 1, Inf];
T = readtable(fullFileName, opts);

rawHeaders = strsplit(headerLineText, ',');
rawHeaders = strtrim(rawHeaders);

nImported = width(T);
nHeaders = numel(rawHeaders);

if nHeaders >= nImported
    T.Properties.VariableNames = matlab.lang.makeValidName(rawHeaders(1:nImported), ...
        'ReplacementStyle', 'delete');
else
    warning('Header row has fewer names than imported columns. Using detected names.');
end

T = rmmissing(T, 'MinNumMissing', width(T));

disp('Detected columns in imported table:');
disp(T.Properties.VariableNames');

%% ---------------- FIND TIME COLUMN ----------------
varNames = T.Properties.VariableNames;
varNamesUpper = upper(varNames);

timeIdx = find(strcmpi(varNames, 'TIME'), 1);
if isempty(timeIdx)
    timeIdx = find(contains(varNamesUpper, 'TIME'), 1);
end

if isempty(timeIdx)
    error('Could not identify a TIME column after import.');
end

timeColName = varNames{timeIdx};
t = T.(timeColName);

if ~isnumeric(t)
    t = str2double(string(t));
end

if all(isnan(t))
    error('TIME column could not be converted to numeric data.');
end

t = t(:);

%% ---------------- FLEXIBLE CHANNEL DETECTION ----------------
candidateIdx = setdiff(1:numel(varNames), timeIdx);
candidateNames = varNames(candidateIdx);

channelPatterns = { ...
    '^CH\d+$', ...
    '^C\d+$', ...
    '^CHANNEL\d+$', ...
    '^CHAN\d+$'};

isLikelyChannel = false(size(candidateNames));

for k = 1:numel(candidateNames)
    nm = upper(strrep(candidateNames{k}, ' ', ''));
    for p = 1:numel(channelPatterns)
        if ~isempty(regexp(nm, channelPatterns{p}, 'once'))
            isLikelyChannel(k) = true;
            break;
        end
    end
end

likelyChannelNames = candidateNames(isLikelyChannel);
fallbackNames = candidateNames(~isLikelyChannel);

fprintf('\nAvailable channels for analysis:\n');
displayNames = [likelyChannelNames, fallbackNames];

for k = 1:numel(displayNames)
    fprintf('  %d -> %s\n', k, displayNames{k});
end

channelChoice = input(sprintf('\nEnter the channel number to analyze (1-%d): ', numel(displayNames)));

if isempty(channelChoice) || ~isscalar(channelChoice) || ...
        channelChoice < 1 || channelChoice > numel(displayNames)
    error('Invalid channel selection.');
end

selectedChannel = displayNames{channelChoice};
x = T.(selectedChannel);

if ~isnumeric(x)
    x = str2double(string(x));
end

if all(isnan(x))
    error('Selected channel could not be converted to numeric data.');
end

x = x(:);

%% ---------------- CLEAN VALID DATA ----------------
validMask = ~(isnan(t) | isnan(x));
t = t(validMask);
x = x(validMask);

if length(t) ~= length(x)
    error('TIME and selected channel have different lengths after cleaning.');
end

if length(t) < 8
    error('Signal is too short for FFT analysis.');
end

%% ---------------- BASIC CHECKS ----------------
dt = diff(t);
dt_med = median(dt);
Fs = 1 / dt_med;
N = length(x);

if any(abs(dt - dt_med) > 1e-3 * max(abs(dt_med), eps))
    warning('Time vector is not perfectly uniform. Using median(dt) to estimate sampling rate.');
end

fprintf('\n================ FFT ANALYSIS SUMMARY ================\n');
fprintf('File name                   : %s\n', fileName);
fprintf('Header row line number      : %d\n', headerRow);
fprintf('Selected channel            : %s\n', selectedChannel);
fprintf('Number of samples           : %d\n', N);
fprintf('Estimated sampling rate     : %.6f Hz\n', Fs);
fprintf('Record length               : %.6f s\n', t(end) - t(1));
fprintf('Frequency resolution        : %.6f Hz\n', Fs/N);
fprintf('Expected switching freq     : %.6f Hz\n', fsw_target);
fprintf('======================================================\n\n');

%% ---------------- DC VALUE ----------------
DC_value = mean(x);

%% ---------------- WINDOWED FFT ----------------
if remove_mean_for_ac_fft
    x_fft = x - mean(x);
else
    x_fft = x;
end

w = hann(N);
coherent_gain = mean(w);

xw = x_fft .* w;
Y = fft(xw);

P2 = abs(Y / (N * coherent_gain));
P1 = P2(1:floor(N/2)+1);

if length(P1) > 2
    P1(2:end-1) = 2 * P1(2:end-1);
end

f = Fs * (0:floor(N/2)) / N;

%% ---------------- dB SPECTRUM ----------------
if normalize_spectrum
    P1_ref = max(P1);
    P1_dB = 20 * log10(P1 / max(P1_ref, epsilon) + epsilon);
    yLabelText = 'Magnitude (dB, normalized)';
else
    P1_dB = 20 * log10(P1 + epsilon);
    yLabelText = 'Magnitude (dB)';
end

%% ---------------- PEAK DETECTION ----------------
is_peak = false(size(P1));
for k = 2:length(P1)-1
    if P1(k) > P1(k-1) && P1(k) >= P1(k+1)
        is_peak(k) = true;
    end
end

peak_threshold = peak_threshold_ratio * max(P1);
is_peak = is_peak & (P1 >= peak_threshold);

peak_freqs = f(is_peak);
peak_amps  = P1(is_peak);

%% ---------------- AUDIO BAND PEAKS ----------------
audio_mask = (peak_freqs >= audio_band(1)) & (peak_freqs <= audio_band(2));
audio_freqs = peak_freqs(audio_mask);
audio_amps  = peak_amps(audio_mask);

if isempty(audio_freqs)
    AudioPeaksTable = table([], [], ...
        'VariableNames', {'Frequency_Hz', 'Amplitude'});
    audio_freqs_top = [];
    audio_amps_top  = [];
else
    [audio_amps_sorted, idx_audio] = sort(audio_amps, 'descend');
    audio_freqs_sorted = audio_freqs(idx_audio);

    num_keep = min(num_audio_peaks, length(audio_freqs_sorted));

    audio_freqs_top = audio_freqs_sorted(1:num_keep);
    audio_amps_top  = audio_amps_sorted(1:num_keep);

    audio_freqs_top = audio_freqs_top(:);
    audio_amps_top  = audio_amps_top(:);

    AudioPeaksTable = table(audio_freqs_top, audio_amps_top, ...
        'VariableNames', {'Frequency_Hz', 'Amplitude'});
end

%% ---------------- SWITCHING COMPONENT ----------------
switch_band_mask = (f >= (fsw_target - switch_search_bw)) & ...
                   (f <= (fsw_target + switch_search_bw));

f_switch_band = f(switch_band_mask);
P_switch_band = P1(switch_band_mask);

if isempty(f_switch_band)
    SwitchFreq_Hz = NaN;
    SwitchAmp = NaN;
else
    [SwitchAmp, idx_sw] = max(P_switch_band);
    SwitchFreq_Hz = f_switch_band(idx_sw);
end

%% ---------------- SIDEBAND ANALYSIS ----------------
SidebandRows = [];

if ~isnan(SwitchFreq_Hz) && ~isempty(audio_freqs_top)
    for k = 1:length(audio_freqs_top)
        fa = audio_freqs_top(k);

        target_lower = SwitchFreq_Hz - fa;
        target_upper = SwitchFreq_Hz + fa;

        [~, idx_lower] = min(abs(f - target_lower));
        [~, idx_upper] = min(abs(f - target_upper));

        lower_freq = f(idx_lower);
        upper_freq = f(idx_upper);
        lower_amp  = P1(idx_lower);
        upper_amp  = P1(idx_upper);

        lower_err = abs(lower_freq - target_lower);
        upper_err = abs(upper_freq - target_upper);

        if lower_err <= sideband_tol || upper_err <= sideband_tol
            SidebandRows = [SidebandRows; ...
                fa, target_lower, lower_freq, lower_amp, ...
                target_upper, upper_freq, upper_amp]; %#ok<AGROW>
        end
    end
end

if isempty(SidebandRows)
    SidebandTable = table();
else
    SidebandTable = array2table(SidebandRows, ...
        'VariableNames', { ...
        'AudioFreq_Hz', ...
        'ExpectedLowerSideband_Hz', 'DetectedLowerFreq_Hz', 'DetectedLowerAmp', ...
        'ExpectedUpperSideband_Hz', 'DetectedUpperFreq_Hz', 'DetectedUpperAmp'});
end

%% ---------------- SUMMARY TABLE ----------------
SummaryTable = table( ...
    {'DC'; 'SwitchingComponent'}, ...
    [0; SwitchFreq_Hz], ...
    [DC_value; SwitchAmp], ...
    'VariableNames', {'Component', 'Frequency_Hz', 'Amplitude'});

%% ---------------- HELPER FOR LABELING ----------------
% Small helper to place readable labels near peaks
labelOffset_dB = 3;

%% ---------------- TIME-DOMAIN PLOT ----------------
figure('Name', 'Time-Domain Signal', 'NumberTitle', 'off');

subplot(2,1,1)
plot(t, x, 'LineWidth', 1.2);
grid on;
xlabel('Time (s)');
ylabel(selectedChannel);
title(sprintf('Time-Domain Signal (%s)', selectedChannel));

% Zoomed view (auto-select ~5 cycles of dominant low frequency if possible)
subplot(2,1,2)

% Estimate a dominant low frequency (for zooming)
if ~isempty(audio_freqs_top)
    f_zoom = audio_freqs_top(1);   % strongest audio component
elseif ~isempty(f) && length(f) > 1
    f_zoom = 1e3; % fallback to 1 kHz
else
    f_zoom = 1e3;
end

T_zoom = 5 / f_zoom;  % show ~5 cycles
t_start = t(1);
t_end = t_start + T_zoom;

zoom_mask = (t >= t_start) & (t <= t_end);

plot(t(zoom_mask), x(zoom_mask), 'LineWidth', 1.2);
grid on;
xlabel('Time (s)');
ylabel(selectedChannel);
title(sprintf('Zoomed View (~%.1f Hz region)', f_zoom));
%% ---------------- PLOT: FULL SPECTRUM ----------------
figure('Name', 'FFT Spectrum - Full Range', 'NumberTitle', 'off');
plot(f, P1_dB, 'LineWidth', 1.2);
grid on;
set(gca, 'YMinorGrid', 'on');
xlim([0 full_plot_max]);
ylim([db_floor, 5]);
xlabel('Frequency (Hz)');
ylabel(yLabelText);
title(sprintf('Single-Sided Amplitude Spectrum (%s)', selectedChannel));
hold on;

% Mark audio band limits
xline(audio_band(1), ':', '20 Hz', 'LabelVerticalAlignment', 'middle');
xline(audio_band(2), ':', '20 kHz', 'LabelVerticalAlignment', 'middle');

% Label switching frequency
if ~isnan(SwitchFreq_Hz)
    [~, idx_sw_full] = min(abs(f - SwitchFreq_Hz));
    plot(SwitchFreq_Hz, P1_dB(idx_sw_full), 'o', 'MarkerSize', 7, 'LineWidth', 1.2);
    text(SwitchFreq_Hz, P1_dB(idx_sw_full) + labelOffset_dB, ...
        sprintf('f_{sw} = %.1f Hz', SwitchFreq_Hz), ...
        'HorizontalAlignment', 'left', ...
        'VerticalAlignment', 'bottom', ...
        'Interpreter', 'tex');
end

% Label strongest audio peaks
num_audio_labels_keep = min(num_labels_audio, length(audio_freqs_top));
for k = 1:num_audio_labels_keep
    fa = audio_freqs_top(k);
    [~, idxa] = min(abs(f - fa));
    plot(f(idxa), P1_dB(idxa), 'o', 'MarkerSize', 6, 'LineWidth', 1.0);
    text(f(idxa), P1_dB(idxa) + labelOffset_dB, ...
        sprintf('%.1f Hz', f(idxa)), ...
        'HorizontalAlignment', 'left', ...
        'VerticalAlignment', 'bottom');
end

% Label sidebands for strongest audio peak
if ~isempty(audio_freqs_top) && ~isnan(SwitchFreq_Hz)
    fa_main = audio_freqs_top(1);

    target_lower = SwitchFreq_Hz - fa_main;
    target_upper = SwitchFreq_Hz + fa_main;

    [~, idx_lower] = min(abs(f - target_lower));
    [~, idx_upper] = min(abs(f - target_upper));

    if abs(f(idx_lower) - target_lower) <= sideband_tol
        plot(f(idx_lower), P1_dB(idx_lower), 's', 'MarkerSize', 7, 'LineWidth', 1.2);
        text(f(idx_lower), P1_dB(idx_lower) + labelOffset_dB, ...
            sprintf('f_{sw} - %.1f', fa_main), ...
            'HorizontalAlignment', 'right', ...
            'VerticalAlignment', 'bottom', ...
            'Interpreter', 'tex');
    end

    if abs(f(idx_upper) - target_upper) <= sideband_tol
        plot(f(idx_upper), P1_dB(idx_upper), 's', 'MarkerSize', 7, 'LineWidth', 1.2);
        text(f(idx_upper), P1_dB(idx_upper) + labelOffset_dB, ...
            sprintf('f_{sw} + %.1f', fa_main), ...
            'HorizontalAlignment', 'left', ...
            'VerticalAlignment', 'bottom', ...
            'Interpreter', 'tex');
    end
end

hold off;

%% ---------------- PLOT: AUDIO BAND ----------------
figure('Name', 'FFT Spectrum - Audio Band', 'NumberTitle', 'off');
audio_plot_mask = (f >= 0) & (f <= audio_band(2));
plot(f(audio_plot_mask), P1_dB(audio_plot_mask), 'LineWidth', 1.2);
grid on;
set(gca, 'YMinorGrid', 'on');
xlim([0 audio_band(2)]);
ylim([db_floor, 5]);
xlabel('Frequency (Hz)');
ylabel(yLabelText);
title(sprintf('Audio-Band Spectrum (%s)', selectedChannel));
hold on;

for k = 1:num_audio_labels_keep
    fa = audio_freqs_top(k);
    [~, idxa] = min(abs(f - fa));
    plot(f(idxa), P1_dB(idxa), 'o', 'MarkerSize', 6, 'LineWidth', 1.0);
    text(f(idxa), P1_dB(idxa) + labelOffset_dB, ...
        sprintf('%.1f Hz', f(idxa)), ...
        'HorizontalAlignment', 'left', ...
        'VerticalAlignment', 'bottom');
end

hold off;

%% ---------------- DISPLAY TABLES ----------------
disp(' ');
disp('==================== SUMMARY TABLE ====================');
disp(SummaryTable);

disp(' ');
disp('========== DOMINANT AUDIO-BAND PEAKS (20 Hz to 20 kHz) ==========');
if isempty(AudioPeaksTable) || height(AudioPeaksTable) == 0
    disp('No dominant peaks found in the 20 Hz to 20 kHz range.');
else
    disp(AudioPeaksTable);
end

disp(' ');
disp('==================== SIDEBAND TABLE ====================');
if isempty(SidebandTable) || height(SidebandTable) == 0
    disp('No sidebands identified using the current tolerance/settings.');
else
    disp(SidebandTable);
end

%% ---------------- OPTIONAL SAVE ----------------
saveTables = input('\nDo you want to save the tables as CSV files? Enter 1 for Yes, 0 for No: ');

if isequal(saveTables, 1)
    [~, baseName, ~] = fileparts(fileName);

    writetable(SummaryTable, [baseName '_SummaryTable.csv']);
    writetable(AudioPeaksTable, [baseName '_AudioPeaksTable.csv']);

    if ~isempty(SidebandTable) && height(SidebandTable) > 0
        writetable(SidebandTable, [baseName '_SidebandTable.csv']);
    end

    fprintf('Tables saved using base name: %s\n', baseName);
end

🧷 Jumper Settings

For this experiment, we will be using only the black board.

Blue Board:

JumperFunctionSettingNote
JP4Carrier waveform selection (Option 1: Constant, Option 2: External Carrier, Option 3: Sawtooth waveform internally generated)Position 3 (we will use internal sawtooth)-
JP5Reference signal selection (Option 1: a dc, whose magnitude can be varied using the potentiometer Rduty1, Option 2: any external signal that ranges between 0 and 5 V, Option 3: Voltage mode (we will use this later on for closed-loop control)Position 2 (we will provide the reference signal from a signal generator)-
JP3The PWM input signal to the deadtime generation circuit can be provided in three ways. (Option 1: Using an external PWM source, for example, an Arduino generating PWM pulses, Option 2: Internal PWM that is generated by the PWM generation circuit, and Option 3: Using current-mode control.)Position 2. We will generate PWM using the internal comparator-
JP1The gate of the high-side MOSFET [qH (in Blue Board) or PWM_H(in Red Board)] can be fed three signals. (Option 1: q1 signal from the dead time compensation circuit, Option 2: gnd, Option 3: q2 signal from the dead time compensation circuit). Note that q1 follows q(t) and q2 is complementary.Position 1. We will use the half-bridge in synchronous mode.
JP2The gate of the low-side MOSFET [qL (in Blue Board) or PWM_L(in Red Board)] can be fed three signals. (Option 1: q2 signal from the dead time compensation circuit, Option 2: gnd, Option 3: q1 signal from the dead time compensation circuit). Note that q1 follows q(t) and q2 is complementary.Position 1. We will use the half-bridge in synchronous mode.

Red Board:

JumperFunctionSettingNote
J7Populating this jumper provides the 12 V supply to the gate driver. (Option 1: 12 V is internally generated, Option 2: An External supply is needed)Position 1. We will provide the internally generated 12V supply to the gate driver.-
J10 and J11These jumpers allow changing the direction of current measurement through the Rsense resistor. (Option 1: Current can be measured flowing from L2 to Vmid terminals, Option 2: Current can be measured flowing from Vmid to L2 terminals)X (we will not be using the internal current sensors)

Black Board:

JumperFunctionSettingNote
Spkr_cnct1Populating this jumper provides power to the speaker.Unpopulated to begin with. We will change this configuration later.-
Gnd_connect1This jumper connects the ground between the power stage and the audio filter stage. X.
JP1This jumper allows you to change the capacitance (C) (Option 1: Not populated, C = 0.47 uF, Position 1: C = 3.77 uF, and Position 2: C = 40.47 uF)As directed in the procedure.

Keep all the other jumpers unpopulated.

⚙️ Circuit Configuration & Setting up the experiment

We will start with the following circuit configuration as shown below. After observing key variables in isolation, we will adapt the circuit to play audio using the speaker. Please make the connections between the red board and external components using the banana cables. Use jumper cables to connect the blue board to the black board. If a current probe is available, we will measure the input current to the external load resistor.

Fig. 2: This experiment will need all the boards (red, blue, black, and purple). We will start with this configuration and then gradually adapt it to observe different parts.

Use the checklist below to mark each step as you complete it. You can download it later on to verify that you have performed all the steps.

Startup & Setup Checklist

🧪 Experiment

Download the checklist above and ensure you have completed all steps before we power on. We will collect a large amount of raw data for post-experiment FFT analysis. A table is provided at the end of this section to help you. So please name your files carefully and maintain a log. We will go through the following steps:

  1. You are most likely probing the signal generator output (the reference signal) on Channel 1, the sawtooth waveform on Channel 2, and q(t) on Channel 3. Let us adjust the time scale to capture at least ten cycles of the reference signal on the scope (total time window of at least 10 ms). Increase the reference signal amplitude to 1 V (peak-to-peak) so that the reference signal now oscillates between 1.5 V and 2.5 V.
  2. Dataset A: Capture the raw data of the signal generator waveform. You should be observing a dc offset (recall we have given a 2 V offset + 1 V at 1 kHz) and a 1 V signal at 1 kHz. Is there any other noise? Also measure the noise floor.
  3. Dataset B: Next, let us capture the raw data of the q(t) signal. Again, note the amplitude at dc, 1 kHz, and other dominant frequencies (note both the frequencies and the corresponding amplitudes), as well as the noise floor.
  4. Dataset C: Change the amplitude of the reference signal to 2 V (peak to peak). Again, capture the raw data to do the FFT of the q(t) signal later.
  5. Revert back to the reference signal amplitude to 1 V (peak to peak).
  6. Dataset D: Move the probe of Channel 3 to measure Hgate(t) in the red board. Recall, in principle, q(t) should be the same as Hgate(t) except that it has a deadtime and a gate drive in between. Again, capture the raw data of the Hgate(t) signal.
  7. Dataset E: Increase the dc source voltage to 10 V. You should observe current flowing into the resistor. Make a note of the current magnitude from the dc source. Measure Vsw node (Red board) and capture the raw data.
  8. Dataset E1: If you have a current probe, measure the current flowing into the load resistor and capture the raw data.
  9. Dataset F: Increase the dc source voltage to 15 V (without changing the reference signal amplitude). You should observe that the current flowing into the resistor is increasing. Voila! You have created your first switch-mode power amplifier! Measure Vsw node (Red board) and capture the raw data.
  10. Dataset F1: If you have a current probe, measure the current flowing into the load resistor and capture the raw data.
  11. Turn off the dc source and the USB power. We are done with the first part of the test. I would recommend creating an FFT script (an example is provided in the code section) that you can run in MATLAB to perform the analysis offline.

Before we proceed, we need to make some changes to the circuit configuration. Here we will bring in the filter as a part of this experiment (that you experimented with in E2). Please go through the following checklist and make a couple of modifcations to your orignial circuit as shown below:

Fig. 3: This experiment will need all the boards (red, blue, black, and purple). In this configuration, we are replacing the external load resistance location. Instead of connecting it to the red board (Vsw and Vlow), we are connecting it to the output of the black board (AC+ and Vlow). The red board output (Vsw) is connected to the black board input (Vsw), and their "Vlow" pins are also connected. This configuration brings the filter between the PWM source and the load.

Circuit Modification Checklist

Download the checklist above and ensure you have completed all steps before we power on. We will go through the following steps:

  1. Power ON the blue board using the USB.
  2. You are most likely probing the signal generator output (the reference signal) on Channel 1, the sawtooth waveform on Channel 2, and q(t) on Channel 3. Let us adjust the time scale to capture at least ten cycles of the reference signal on the scope. Increase the reference signal amplitude to 1 V (peak-to-peak) so that the reference signal now oscillates between 1.5 V and 2.5 V.
  3. Dataset G: Increase the dc source voltage to 10 V. You should observe current flowing into the resistor. Make a note of the current magnitude from the dc source. Move the probe of Channel 3 to measure Vsw node (Black board) and capture the raw data.
  4. Dataset H: Move the probe of Channel 3 to measure Vac in the black board (output of the filter). Ensure no jumper is connected to JP1 (black board). This will ensure that C=0.47 uF. Measure Vac node (Black board) and capture the raw data.
  5. Dataset H1: If you have a current probe, measure the current flowing into the load resistor and capture the raw data.
  6. Dataset I: Place a jumper in position 1 for JP1 (black board). This will ensure that C=3.77 uF. Measure Vac node once again (Black board) and capture the raw data.
  7. Dataset I1: If you have a current probe, measure the current flowing into the load resistor and capture the raw data.
  8. Dataset J: Place a jumper in position 2 for JP1 (black board). This will ensure that C=40.47 uF. Measure Vac node once again (Black board) and capture the raw data.
  9. Dataset J1: If you have a current probe, measure the current flowing into the load resistor and capture the raw data.
  10. Turn off the dc power supply and power off the blue board using the USB.
  11. Remove the load resistor from AC+ (Black board) and Vlow.
  12. Populate the jumper Spkr_Cnct1. Now, we are going to the next phase, with all the elements connected (class D amplifier, filter, and speaker).
  13. If you have a current probe, measure the current flowing into the speaker.
  14. Power on the USB and Turn ON the dc power supply to 10 V.
  15. Dataset K: Use Channel 3 to measure Vac in the black board (output of the filter). Ensure no jumper is connected to JP1 (black board). This will ensure that C=0.47 uF. Measure Vac node (Black board) and capture the raw data.
  16. Dataset K1: If you have a current probe, measure the current flowing into the speaker and capture the raw data. Hear carefully and note how the sound appears to you.
  17. Dataset L: Place a jumper in position 1 for JP1 (black board). This will ensure that C=3.77 uF. Measure Vac node once again (Black board) and capture the raw data. Measure Vac node (Black board) and capture the raw data.
  18. Dataset L1: If you have a current probe, measure the current flowing into the speaker and capture the raw data. Hear carefully and note how the sound appears to you.
  19. Dataset M: Place a jumper in position 2 for JP1 (black board). This will ensure that C=40.47 uF. Measure Vac node once again (Black board) and capture the raw data. Measure Vac node (Black board) and capture the raw data.
  20. Dataset M1: If you have a current probe, measure the current flowing into the speaker and capture the raw data. Hear carefully and note how the sound appears to you.
  21. You are most likely probing the signal generator output (the reference signal) on Channel 1, the sawtooth waveform on Channel 2, Vac(t) on Channel 3, and the current probe, if available, on Channel 4. Let us adjust the time scale to 400 ms time scale.
  22. The last part of the experiment is actually fun! Set up your signal generator in sweep mode. The amplitude remains the same. Start frequency 20 Hz, Stop frequency 20000 Hz, linear sweep, and sweep time 1 sec. This setting will automatically sweep the frequencies. Hear closely how the sound comes across as you change the capacitors (C). Note it down. Also, grab a screenshot of the scope for each case (a) no jumper on JP1 (black board), (b) jumper on position 1 for JP1 (black board), and (c) jumper on position 2 for JP1 (black board).
  23. Please verify you have all the data stored carefully with proper tag so as to be able to do the FFT offline.

Data Acquisition Summary (Experiment E3)

Dataset Condition / Change What to Probe What to Observe
A Reference signal (1 Vpp, 2 V offset, 1 kHz) CH1: Reference DC (~2 V), 1 kHz tone, noise floor
B PWM generation CH1: Reference, CH2: Sawtooth, CH3: q(t) DC, 1 kHz, switching frequency (~100 kHz), harmonics
C Increase reference to 2 Vpp CH3: q(t) Increase in modulation → stronger 1 kHz component
D Probe Gate driver output CH3: Hgate(t) Compare q(t) vs Hgate(t), observe dead-time and effect of gate driver
E DC = 10 V (no filter) CH3: Vsw (Red Board) Switching waveform, DC + ripple
E1 Optional current measurement CH4: Current Current into resistor
F DC = 15 V CH3: Vsw (Red Board) Increased amplitude → higher power
F1 Optional current measurement CH4: Current Increase in load current
G Reconfigured to Black Board, DC = 10 V CH3: Vsw (Black Board) Switching waveform after routing
H C = 0.47 µF (no jumper) CH3: Vac Partial filtering, noticeable ripple
H1 Optional current measurement CH4: Current Current waveform into load
I C = 3.77 µF (JP1 pos 1) CH3: Vac Improved filtering, cleaner waveform
I1 Optional current measurement CH4: Current Smoother current
J C = 40.47 µF (JP1 pos 2) CH3: Vac Strong filtering, near sinusoidal output
J1 Optional current measurement CH4: Current Reduced ripple current
K Speaker connected, C = 0.47 µF CH3: Vac Audible distortion, poor filtering
K1 Optional speaker current CH4: Current Current into speaker
L Speaker, C = 3.77 µF CH3: Vac Improved sound quality
L1 Optional speaker current CH4: Current Smoother current
M Speaker, C = 40.47 µF CH3: Vac Best sound quality (least distortion)
M1 Optional speaker current CH4: Current Cleanest current waveform

Turn off Checklist

Before we close the experiment, please ensure:

Turn-Off & Shutdown Checklist

🧠 Observations & Analysis

Now, let us analyse the data you collected to write a brief report that solidifies our understanding. Structure the report to include the following:

  1. Objective: What are the objectives of this experiment? Clearly restate them in your own words. Your response should reflect how a low-power sinusoidal reference signal is converted into a high-power ac signal using a Class-D amplifier, the role of the modulator in generating the switching signals q(t)q(t) and q(t)q'(t), how the half-bridge stage enables power amplification without altering the frequency content, and why it is important to analyze both time-domain waveforms and frequency-domain (FFT) representations to evaluate signal quality.
  2. Theory: Explain the role of each stage in the signal chain and the expected system behavior. Your explanation should include the function of the modulator (comparison of the reference signal with the carrier to generate PWM), the role of the half-bridge in producing a high-power switching waveform VswV_{sw}, the function of the output filter in removing switching harmonics and reconstructing the sinusoidal signal, and how the load (resistor or speaker) interacts with the filter and influences the final output.
  3. Using the dataset A-E, plot the time-domain waveforms and corresponding FFTs (magnitude in dB) for the reference signal, q(t)q(t), VswV_{sw}​, Hgate, and current. Analyze the results by identifying the dc component and the 1 kHz signal in each case, locating the switching frequency (~100 kHz) and its sidebands, explaining how the spectrum evolves from the reference signal to PWM, to the switch node, and finally to the filtered output, and describing how the filter attenuates high-frequency components. Compare the time-domain waveform with its corresponding frequency-domain representation.
  4. Effect of Reference Amplitude: Using datasets B and C, analyze the effect of increasing the reference signal amplitude. Discuss how the duty ratio of q(t)q(t) changes, how the amplitude of the 1 kHz component changes in the FFT, whether clipping occurs when the reference exceeds the carrier limits, and how the modulation depth affects the reconstructed output signal.
  5. Effect of dc bus voltage: Using datasets E and F, analyze the effect of increasing the dc source voltage. Discuss how the amplitude of VswV_{sw} changes, how the output voltage and current scale with the DC voltage? Does the frequency content remain unchanged while the amplitude increases? What does this imply about the role of the power stage?
  6. Power drawn from the DC source with and without the filter: Compare the current drawn from the DC source in test cases E and G. Why is the current drawn different?
  7. Effect of the filter: Using datasets H-J, compare the results for C=0.47μFC = 0.47\,\mu\text{F}, C=3.77μFC = 3.77\,\mu\text{F}, and C=40.47μFC = 40.47\,\mu\text{F}. Discuss how well the sinusoidal waveform is reconstructed in each case, how much switching ripple is present in the output, how the FFT changes with different capacitance values, which case provides the best trade-off between filtering and responsiveness, and which case allows more high-frequency components to pass through, and explain why.
  8. Effect of the speaker as a load: Using datasets K-M, when the load is replaced with a speaker, analyze both electrical and perceptual results. Discuss how the sound quality changes with different capacitor values, how waveform distortion relates to audible distortion, why better filtering improves sound clarity, and how the electrical measurements correlate with what you hear.
  9. Frequency sweep analysis: Using the sweep from 20 Hz to 20 kHz, analyze the system response. Discuss how the output varies across frequency, whether low-frequency attenuation or high-frequency attenuation is observed, how the response changes with different capacitor values, and what this reveals about the bandwidth and limitations of the system. Can you identify the points of mechanical and electrical resonances?
  10. Practical Implications: Based on your observations, discuss the practical implications by explaining why filtering is essential in a Class-D amplifier, how filter design depends on switching frequency and load characteristics, how the different stages (modulation, power stage, and filter) interact, and why FFT analysis is critical for evaluating signal quality.
  11. Conclusion: Summarize the key takeaways from this experiment. Your conclusion should reflect how PWM enables signal encoding, how the half-bridge enables power amplification, how the output filter reconstructs the sinusoidal signal, how component choices affect performance and distortion, and how the experimental observations align with theoretical expectations.

✔ Conclusion

This lab demonstrated how a Class-D amplifier converts a low-power signal into a high-power ac output using PWM, a half-bridge, and an output filter. The results showed that the filter is a frequency-selective system whose performance depends on component values and the load, requiring a balance between ripple attenuation, fidelity, and dynamic response.