E58 Control Theory: Final Project
Hovering Above Failure
in partnership with aron dobos, david luong and mark piper
May 5, 2005

labs home    |    lab1    |    lab2    |    lab3    |    lab4    |    lab5    |    lab6    |    finale


Our goal was to build a small hovercraft and have it respond favorably to motion and direction changes.  By this we mean implementing a PID controller that ensures that the transient responses of the fans from input voltage changes are quick, stable, and smooth.  A pulse width modulation circuit was designed and built on a breadboard to provide the power input to the four fans.  Digital to analog conversion hardware was used to acquire and process the data and controllers were implemented in MATLAB using the NIDAQ interface.  In the end, we were able to joystick operate the hovercraft with good responses in hovering height as well as directional changes. 

A grander application of our work would be an extension on real hovercrafts to provide smoother and more comfortable rides for passengers.

Figure 0.  Hovercraft, Power Supplies, PWM Circuitry, Joystick.

Hovercraft Design

The basic materials were obtained from a hovercraft kit that was purchased on the Internet.  Styrofoam trays served as a light-weight hovercraft body and frame, and were easy to modify for placements of four fans.  The fans came assembled with motor and rotors attached and were operational once a voltage and ground connection was made.  The four motors were placed in the corners of the rectangular tray, and a sensor was placed along with each.  This was chosen to maximize the usefulness of the sensors in detecting tilts and heights changes in the craft.  Since they are quite sensitive, spacing the sensors apart would minimize their effects one another and thus increase their measurement effectiveness in detecting localized height variations on the craft.

The fans and sensors were attached to the body simply by paper fasteners.  A ribbon wire '‘umbilical cord' provided power to the motors and carried the sensor signals to the amplifiers.

The hovercraft as designed tended to rotate when it operated due to a positive sum of torques about the center.  This could not be mended because the rotor and motor came pre-assembled, and the fans were not bi-directionally symmetrical.


We used Sharp GP2D12 general purpose distance measuring infrared sensors to detect the elevation of our hovercraft.  Although the GP2D12 usually operates between 10 and 80cm, we found that it outputs a linear voltage between 2.5 - 3.5 cm.  Figure 1 shows the voltage output in this range.  Although there were some variations between the sensors, we found that this linear range exists for all of the sensors we used.  The sensors were sensitive to a change of 1mm. and the noise in the readings was very low.

Figure 1. Voltage reading of the Sharp GP2D12 IR sensor.

Figure 2. Sharp GP2D12 general purpose distance sensor. Courtesy of Acroname Inc.


We used a joystick to guide our hovercraft.  An old incapacitated joystick was disassembled and rewired to give direct access to the coordinate potentiometers.  The center taps were used to provide a variable voltage between 0 and 5 V.  The joystick also had a wheel that was used to control the elevation of the hovercraft.  The stick returned voltages between 0 and 5 V whereas the wheel returned values between 0.6 and 5 V.  Both of these readings were linear. Figure 3 shows the voltage readings of the stick. The joystick reading is (2.5V, 2.5V) when the stick is at rest, so in controlling the directional movement, we first subtracted (2.5V, 2.5V) from the readings so that the input due to the joystick would be zero if it was at rest. Figure 4 shows the effective readings.

Figure 3.  Voltage readings for the stick in various directions/locations.

Figure 4. Effective voltage readings.

Directional Movement

Once the voltages from the joystick were adjusted, we had to find a way to send the right voltages to the motors so that the hovercraft would move forward when the joystick was pushed forward. We considered a couple of different ways to move the hovercraft.  Firstly, we thought about increasing the speed of the motors in the opposite side only without decreasing the speed of the front motors.  However, we decided not to implement this method, because when the hovercraft was at a high elevation, the motors would be running at close to maximum voltage and it would be really hard to move it forward with a very small difference in the back-side and front-side motors.  So we decided to implement a control where the hovercraft would decrease the voltage in the front-side motors while increasing the voltage on the rear motors to go forward.  This method also lets the hovercraft move faster.  The problem with this method is that when the hovercraft is at a low elevation then decreasing the voltage on the front motors can cause the front side of the hovercraft touch the ground.  However, this is not a big issue since the captain can adjust the height prior to moving forward.  Also, the hovercraft is light enough and it hovers slightly even when the propellers are turning slowly.

To find the input voltages due to the joystick, we created functions for each motor taking into account sideways motion as well as forward motion.  For instance, let’s consider motor 1 (the numbers of the motors is given in Figure 5)  The voltage due to the joystick would be

where kpis a proportionality constant.

The inputs due to joystick for other motors are similar.  If we consider motor a 1x4 vector with the rows representing each motor in order the control is as follows.

Figure 5. The numbers of motors on the hovercraft.

PWM Motor Drive Circuitry

A twin-opamp Schmitt trigger/Miller integrator oscillator provides a -5 to 5 V p-p triangle wave at approximately 70 Hz to the 411 summation circuit that adjusts the gain and adds a DC offset.  The final output of the oscillator circuit is a 0-5V triangle wave.  This signal is fed into four separator 311 comparators that compare the triangle wave to a DC voltage input coming from the NIDAQ acquisition board.  If the DC input signal is small, the output of the comparator will be high most of the time, and vice versa.  Therefore, the output from the comparator will be a variable duty-cycle square pulse at 70 Hz.  In this way pulse width modulation is achieved.  The PWM signal is buffered by the 7407 and controls the solid state relay and hence the motor.  The comparator/buffer/relay circuit is repeated four times identically, providing a system with four separate inputs to four corresponding motors.  The complete circuit schematic is shown below in Figure 6.

Figure 6.  PWM Motor Drive Circuitry.

Sensor Amplification Circuitry

A 411 opamp is used in a non-inverting configuration to double the output voltage of the IR sensors.  Given the limited range in which the IR sensor was used, this circuitry resulted in an output voltage of approximately 1.5 – 3.5 V, which was then sent to the A/D converters on the NIDAQ board.  The 1uF capacitor filtered out high frequency noise to help cleanup the somewhat noisy sensor output. The amplification circuit is shown below in Figure 7.  Four of these circuits were constructed, one for each sensor.

Figure 7. Sensor Amplification Circuit.

NIDAQ Board Interface

The NIDAQ board provides only two D/A outputs.  To control four fans, two of the available 8-bit digital ports were used alongside the two D/A outputs.  External D/A circuitry was implemented using the standard configuration of the DAC0808, with a rail-to-rail opamp (TLV2772) to convert the output current of the DAC0808 to a 0-5 V signal.  The schematic can be found in the DAC0808 datasheet, as the circuit given was used without modifications.

The first four A/D inputs of the NIDAQ board were allocated to the four sensors.  The next three inputs read the voltages of the joystick potentiometers.  Therefore, the final MIMO system consisted of 4 system feedback inputs, 3 control inputs, and 4 motor outputs.

Controller Selection and Test Results

To control the hovercraft’s height above the surface, a PID controller and variations on it were used.  Choosing constants was somewhat arbitrary, as no system characterization was performed due to the overall complexity of the system, including especially the mutual interference of the air flows underneath the craft.  Given an understanding of how the various components of the PID controller affected the closed-loop response of the system, the constants were adjusted appropriately to obtain the desired response.

The desired characteristics of the hovercraft response are given below in order of decreasing priority.

  1. Minimal overshoot and ‘jumping’ at a level steady state height.   If the hovercraft is hovering without translating, for example when it is docked in harbor, it is critical that the vessel not bounce up and down to keep any passengers from becoming seasick as well as facilitating the loading of supplies, maintenance, and other tasks.  Stable steady state was the original design goal of this project.
  1. Fast reaction to changes in joystick position.  This increases the agility of the hovercraft, and makes it more maneuverable around potential obstacles or hazards.
  1. Zero-steady state error.  To make some sort of computer-controlled ‘autopilot’ possible, it would probably be necessary for the hovercraft to reach a specified height exactly.

The progression of the controller design is summarized in Table 1 below.





Observed Response

Adjustment for next controller





Good S.S. stability, fans don’t respond sometimes to joystick inputs until integral term kicks in

Increase ki to make integration happen more quickly





Same, but some fans still don’t respond well

Try removing integral control





Good rise time, but overshoots and there is some ringing.  No longer 0 SS error.

Add some derivative to lessen the overshoot





Extremely bouncy at steady state, good joystick response

Decrease the derivative control to avoid output spikes due to large differentials in error signal





Still extremely bouncy, good joystick response

Decrease derivative more





Nice response overall, but no 0 SS error.

Increase gain to decrease SS. Error





Gain too large, jumps around at SS.

Decrease gain





Best response given the parameters.


Table 1. Progression of Controller Design.

Graphs of the sensor voltages and the error signals for each of the four motor/sensor pairs on the hovercraft are included below for the final controller. 

Figure 8.  Sensor voltages for various input modes.

Figure 9. Error signals for the various input modes.

The four sensor voltages should ideally match up because they should be theoretically placed at the same distance from the ground.  However, as seen in the figures above, that is obviously not the case.  Possible reasons for this include the sensors not residing at a common distance from the ground and the fact that the four motors and the sensors may not be well matched. 

Self-Preserving Nature of our Craft

In one test, the hovercraft was directed to move off the edge of the table by the joystick operator.  As soon as one of the fans cleared the edge, the fan speed increased immediately and pushed the hovercraft back onto the table.  This cycle repeated itself several times, and the rather amusing sensor voltage graph is given in Figure 10.  It is always a good feature of a hovercraft that it refuses to fall off cliffs.  This ‘feature’ is documented in one of the movie clips accompanying this report.

Figure 10. Self-Preserving Hovercraft.


The hovercraft performed acceptably with our final controller.  Zero steady state error was not achieved, but the other response characteristics were met.  For a real hovercraft upon which the loads vary continually as people move about, a controller that gives zero steady state error would be necessary to keep the craft off the ground and above the waves.  Perhaps a modern state-space control system would be able to give exactly the desired response and zero steady state error.  Other future work could involve separating the air chambers on the underside of the hovercraft to minimize the interference of airflows from the various fans.  Another small but useful improvement would be to replace two of the fan blades with left-handed propellers and spin those two motors in the opposite direction.  Then, the sum of torques about the center of mass of the craft would sum to zero at steady state, and eliminate the rotation that was experienced by our craft.

Action Clips:


MATLAB code.

function [] = joystick( SampleTime, k_p, k_d, k_i)
% plots joystick x-y-z data
% Engineering 58 Final Project
% David Luong, Mark Piper, Adem Kader, Aron Dobos
% Set up sampling parameters
SampleFreq = 30;         %Sampling Frequency
if SampleTime*SampleFreq > 2000     disp(' too many data points ');     return end
NumPts=SampleFreq*SampleTime;  %Number of points in run (<=2000). DT=1/SampleFreq;               %Sampling Interval disp(sprintf('Frequency: %d Hz, Time: %d s, Points: %d',SampleFreq, SampleTime, NumPts));
% Setup the input channel % You shouldn't need to change this. Ain = analoginput('nidaq', 1); Ain.SampleRate = SampleFreq; Ain.SamplesPerTrigger = NumPts; Ain.InputType = 'SingleEnded'; Ain.BufferingConfig = [1 2000]; Ain.TransferMode = 'Interrupts'; disp('Input Channel Configuration'); InChannels  = [ 0 1 2 3 4 5 6 ];                % Input channels to use on DAQ board DaqChannels = [ 7 6 5 4 3 2 1 ];                % mapping to correct MATLAB indicies chans_in = addchannel(Ain,InChannels); get(chans_in,{'HwChannel','Index','Parent','Type'}) disp('Output Channel Configuration');
% define the output channels (2 digital, 2 analog) Dio = digitalio('nidaq', 1); addline(Dio, 0:15, 0, 'out'); disp(' -- 2 digital ports 8-bit: PA, PB -- '); Aout = analogoutput('nidaq'); chans_out = addchannel(Aout,[0 1]); get(chans_out,{'HwChannel', 'Index', 'Parent', 'Type'})
% predefine data arrays joyxdata = zeros(NumPts, 1); joyydata = zeros(NumPts, 1); joyzdata = zeros(NumPts, 1); sensorvals = zeros(4, NumPts); rawsensors = zeros(4, NumPts); errsig = zeros(4, NumPts); inputvals = zeros(4, NumPts);
% controller data values integrators = zeros(4, NumPts); derivatives = zeros(4, NumPts);
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % define Time Vector (time=0 after PreSample is done) t = (1:NumPts)*DT; LastIndex=0; JThres = .5; NotifyInterval = 5; nNotifications = 1; start(Ain);
while (Ain.SamplesAcquired < NumPts),     j=Ain.SamplesAcquired;     while(LastIndex==j),  %Wait for next datum.         j = Ain.SamplesAcquired;     end
    if ((j-LastIndex)~=1),          % Make sure no data was missed.         disp(sprintf('Warning, datum skipped: %d, %d',LastIndex,j));     end     InData = peekdata(Ain,j);       % Retrieve most recent datum.     rawsensors(1, j) = InData(j, DaqChannels(1));     rawsensors(2, j) = InData(j, DaqChannels(2));     rawsensors(3, j) = InData(j, DaqChannels(3));     rawsensors(4, j) = InData(j, DaqChannels(4));     % do a little sensor averaging to smooth out some of the spikes     if (j>2)         for k=1:4             rawsensors(k, j) = (rawsensors(k, j)+rawsensors(k, j-1)+rawsensors(k, j-2)) / 3;         end     end
% JOYSTICK READINGS (V) %    (5,5) %     O-------O-------O (5,0) %     |       |       | %     |       |       | %     |       |       | %     O-------O-------O %     |       |       | %     |       |       | %     |       |       | %     O-------O-------O (0,0)
    joyxdata(j) = InData(j, DaqChannels(5)) - 2.5;     joyydata(j) = InData(j, DaqChannels(6)) - 2.5;
    if (joyxdata(j) < JThres && joyxdata(j) > -JThres)         joyxdata(j) = 0;     end
    if (joyydata(j) < JThres && joyydata(j) > -JThres)         joyydata(j) = 0;     end
    joyzdata(j) = InData(j, DaqChannels(7));
    if (mod(t(j),NotifyInterval)==0)         disp(sprintf('Still acquiring data, %d seconds elapsed...', nNotifications*NotifyInterval));         nNotifications = nNotifications+1;     end
    % normalize the sensors     sensorvals(1,j) = ( rawsensors(1,j) - 1.82 ) / 2.24;     sensorvals(2,j) = ( rawsensors(2,j) - 1.93 ) / 2.47;     sensorvals(3,j) = ( rawsensors(3,j) - 1.90 ) / 2.33;     sensorvals(4,j) = ( rawsensors(4,j) - 1.72 ) / 2.22;     sensorvals(:,j) = sensorvals(:,j) .* 5;     inputvals(1:4,j) = (joyzdata(j)-.5)*[1 1 1 1];
    % do input from joystick X axis     inputvals(1:4,j) = inputvals(1:4,j) + 1.5*joyxdata(j)*[-1 1 1 -1]';
    % do input from joystick Y axis     inputvals(1:4,j) = inputvals(1:4,j) + 1.5*joyydata(j)*[1 1 -1 -1]';     errsig(:,j) = inputvals(:,j) - sensorvals(:,j);
    for k=1:4         proportional = k_p*errsig(k,j);         derivative = 0;         if (j>1)             derivative = k_d*(errsig(k,j)-errsig(k,j-1))/DT;             integrators(k) = integrators(k) + (errsig(k,j)+errsig(k,j-1))*DT/2;         else             integrators(k) = integrators(k) + errsig(k,j)*DT/2;             derivative = k_d*errsig(k,j);         end         integral = k_i*integrators(k);         outputs(k) = proportional+derivative+integral;     end
    outputs = 5 - outputs;
    %outputs = [ 5 5 5 5 ];     % scale v1, v2 for the digital lines     v1 = outputs(1)/5*255;     v2 = outputs(2)/5*255;     v3 = outputs(3);     v4 = outputs(4);
    % do some bounds-checking on the final output values     if (v1 < 0)         v1 = 0;     elseif (v1 > 255)         v1 = 255;     end     if (v2 < 0)         v2 = 0;     elseif (v2 > 255)         v2 = 255;     end     if (v3 < 0)         v3 = 0;     elseif (v3 > 5)         v3 = 5;     end     if (v4 < 0)         v4 = 0;     elseif (v4 > 5)         v4 = 5;     end
    % write the data values;     putvalue(Dio.Line(1:8), dec2binvec(v1, 8) );     putvalue(Dio.Line(9:16), dec2binvec(v2, 8) );     putsample(Aout, [v3 v4]);     LastIndex = j; end
% turn off the motors putvalue(Dio.Line(1:8), dec2binvec(255, 8) ); putvalue(Dio.Line(9:16), dec2binvec(255, 8) ); putsample(Aout, [5 5]);
% Free up any memory that we used. delete(Ain); clear Ain delete(chans_in); clear chans_in; delete(chans_out); clear chans_out; delete(Dio); clear Dio; delete(Aout); clear Aout; plot(t, sensorvals(1,:), t, sensorvals(2,:), t, sensorvals(3,:), t, sensorvals(4,:)); title(sprintf('Sensor Voltages: k_p=%.2f k_d=%.2f k_i=%.2f', k_p, k_d, k_i))
legend('1', '2', '3', '4');
xlabel('Time (s)');
filename = sprintf('testpid_%.2f_%.2f_%.2f_SV.bmp', k_p, k_d, k_i);
print('-dbitmap', filename);
plot(t, errsig(1,:), t, errsig(2,:), t, errsig(3,:), t, errsig(4,:)); title(sprintf('Error Signals: k_p=%.2f k_d=%.2f k_i=%.2f', k_p, k_d, k_i)) legend('1', '2', '3', '4'); xlabel('Time (s)'); ylabel('Volts'); filename = sprintf('testpid_%.2f_%.2f_%.2f_ERR.bmp', k_p, k_d, k_i); print('-dbitmap', filename);
s1mean = mean(sensorvals(1,:)) s2mean = mean(sensorvals(2,:)) s3mean = mean(sensorvals(3,:)) s4mean = mean(sensorvals(4,:))
% figure % plot(joyxdata, joyydata); % title('X-Y joystick input data'); % axis([0 5 0 5]); % xlabel('X Voltage'); % ylabel('Y Voltage');
% figure
% plot(t, joyzdata); % title('Z-Axis vs. Time');
% xlabel('Time');
% ylabel('Voltage');