Project 3: Pulse-Width Modulation and Proportional-Derivative Control
- Project checkpoint is due by Thursday, April 5th at 5:00pm.
- All components of the project are due by Thursday, April 12th
at 5:00pm.
- Discussion within groups is fine.
- Discussion across groups may not be about the specifics of the
solution (general programming/circuit issues are fine to
discuss).
Project Goals
At the end of this project, you should be able to:
- control the speed and direction of DC motors through an H-bridge circuit,
- implement and tune a proportional-derivative control law that
maintains the hovercraft's heading at some desired orientation, and
- implement a high-level control law that decides when to brake
and when to use the PD control law.
Project Components
All components are required to receive full credit for the project.
Part 1: Microcontroller Circuit
The current amplifier board is composed of two full H-Bridge
circuits. We will be using one full H-Bridge to control the middle
fan (allowing us to control rotation speed and direction). We will
split the other H-Bridge into two "Half Bridges": one for each of the
left and right fans. This will allow us to control speed of these two
fans, but not direction.
The detailed documentation for the motor control board can be found on the
Lab
Hardware page: see the Motor Control Board section. The
high-level view is given below.
Add wires to connect the motor control board to the fans and to the batteries:
- Connect GND and VIN directly to the battery (not to your
5V regulated power supply!!!)
- Connect the middle fan to port 2 (OUT 2A and 2B)
- Connect the right fan: red wire to OUT 1A and black wire to GND
- Connect the left fan: red wire to OUT 1B and black wire to GND
Connect the current amplifier board to your Atmel chip (the 15 pin
connector on the left side of the board in the picture below):
- Connect 1PWM and +5V(IN) to your Atmel's +5V power supply
- Connect GND and GND to your Atmel's ground
- Choose one of: timer1, 3, 4 or 5. For the chosen timer, you must have
all three PWM pins available (they are labeled OCXA, OCXB and
OCXC on the Arduino circuit, where X = 1, 3, 4 or 5).
- Connect the following to your Atmel (do NOT use B0 ... B3):
- 1INA: Pulse-Width Modulated (PWM) input for the right
fan. Connect to OCXA
- 1INB: PWM input for the left fan. Connect to OCXB
- 2PWM: PWM input for the middle fan. Connect to OCXC
- 2INB/2INA: direction control for the middle fan (0/1:
one direction; 1/0: the other direction). Connect to
free digital output pins.
Part 2: Fan Control Interface
Note: this part will count for a total of one personal
programming credit
Create the function interface that will generate the PWM signals
for each of the three PWM inputs to the motor control board.
Define two new variable types in "project.h":
typedef enum {
BRAKE,
HOVER
} MotorDirection;
typedef enum {
LEFT = 0,
RIGHT = 1
}SensorSide;
MotorDirection and SensorSide are the variable types. BRAKE and HOVER
are the two values that MotorDirection variables can take on. LEFT and
RIGHT are the two values that SensorSide variables can take on.
Remember to include "project.h" in your C file.
Implement the following functions:
-
void set_middle_direction(MotorDirection direction) that sets the
direction bits for the middle fan.
If direction == HOVER, then the craft should hover (assuming an
appropriate level of thrust).
If direction == BRAKE, then the craft should be pulled to the ground.
- void set_middle_magnitude(int16_t magnitude) that sets
the thrust magnitude for the middle fan. This function must
ensure that magnitude falls within the range of 0... 1023
(which correspond to 0% ... 100% duty cycle). If it does not,
then the value should be clipped to this range.
- void set_lateral_magnitudes(int16_t magnitude_left, int16_t
magnitude_right) that sets the thrust magnitude for the left
and right fans. This function must
ensure that magnitude_left and magnitude_right fall
within the range of 0... 1023 (which correspond to 0% ... 100%
duty cycle). If either does not, then the offending value
should be clipped to this range.
Note:
- Initialization of the three PWM channels must happen within
your main() function (see the lecture notes on timers).
Testing
Implement the following functions:
Modify your main function such that it executes one of the two
above functions, depending on the initial state of switch 0.
Part 3: Proportional Derivative Control
Note: this part will count for one personal programming credit
Create a function that will implement a proportional-derivative
controller:
void pd_control(int16_t error, int16_t rotation_rate, uint16_t forward_thrust)
Where:
- error is the heading error,
- rotation_rate is the rate of craft rotation, and
- forward_thrust is the total forward thrust is the sum of the left/right thrust magnitudes (this
latter value will be between 0 ... 1023).
This function will:
- compute a left/right differential control signal for the fans
using the proportional-derivative control law. This control
law must include the heading error and current rotation rate.
Within this function, heading error must take into account a small
dead-band around zero error and maximum error values (a good place to start is: dead-band +/- 5 degrees; and max error +/- 45 degrees).
- add this differential control signal to the forward_thrust
input.
- set magnitude_left and magnitude_right.
Note: remember that integer math is much more efficient. However, you
have to be more careful about your data types and the order of your
multiplication and division operations.
Notes on tuning the PD control parameters:
- Start with Kp = 0, and slowly bring Kv up to a reasonable
level. Your craft should resist rotation. If it accelerates
instead, then you probably have the sign of Kv wrong.
- Next, set Kv = 0, and slowly bring Kp up to a reasonable
level. You will see oscillations (this is ok for the
instant). If the craft turns away from the goal, they you have the sign
wrong on Kp.
- Now use your selected Kp and Kv. When you have oscillations,
your choices are to: increase Kv or reduce Kp. Make adjustments slowly.
- There is such a thing as Kp being too high. This comes from the
fact that from one control decision to the next there is some
amount of delay. The larger the delay, the larger the
oscillations. In this case, your only choice is to drop Kp
(and likely Kv, too).
Part 4: High-Level Control
Note: this part will count for one personal programming credit
For this part, you will implement a high-level control loop that will
cycle once every 50 ms. For each of these control steps, if
no obstacles are visible, the craft will hover and move forward. If
an obstacle is detected, then the craft should brake.
-
Modify the your program so that it is structured as follows
(you will, of course, need to add other code):
// Global flag
volatile uint8_t flag_timing = 0;
// Interrupt service routine: called every time
// the timer 0 counter transitions from 0xFF to 0.
// Period of flag_timing is 3 * 256 * 1024 / 16000000 = 49.152 ms
ISR(TIMER0_OVF_vect) {
static uint8_t count = 0; // Set to zero at beginning of program only
if(++count == 3){
// Set the flag to indicate that the period has passed
flag_timing = 1;
count = 0;
}
};
int main(void) {
int16_t counter = 0;
int16_t heading, heading_last, heading_goal, heading_error;
int16_t rotation_rate, distance_left, distance_right;
#APPROPRIATE VARIABLE DECLARATIONS HERE#
#APPROPRIATE INITIALIZATIONS HERE#
timer0_config(TIMER0_PRE_1024); // Prescale by 1024
timer0_enable(); // Enable the timer 0 overflow interrupt
sei(); // Enable global interrupts
// Initialize variables
heading_goal = get_heading();
distance_left = get_ir_distance(LEFT);
distance_right = get_ir_distance(RIGHT);
// Begin to lift off the ground
set_middle_direction(HOVER);
#RAMP UP MIDDLE THRUST TO HOVER#
// Loop for ~30 seconds
while(counter < 20*30) {
heading = get_heading();
heading_error = compute_heading_error(heading, heading_goal);
rotation_rate = get_rotation_rate();
distance_left = get_ir_distance(LEFT);
distance_right = get_ir_distance(RIGHT);
// Display
#APPROPRIATE CODE FOR DISPLAYING SENSOR STATES WITH YOUR LEDS#
// Obstacles?
if(#obstacles#) {
#BRAKE#
}else{
// No obstacles: steer to desired heading
pd_control(heading_error, rotation_rate, #PICK AN APPROPRIATE VALUE#);
}
// Increment time
++counter;
if(flag_timing) {
// Error condition: your code body is taking too much
// time.
#Indicate this with an LED display of some form#
}
// Wait for the flag to be set (happens once every ~50 ms)
while(flag_timing == 0) {};
// Clear the flag for next time.
flag_timing = 0;
}
#RAMP DOWN MIDDLE THRUST TO ZERO#
while(1){}; // Spin forever
}
Part 5: Hovercraft Layout
Revisit the mounting of your components on the Frisbees:
- Make sure that all components and cables are secure and that no
wires are near the fans.
- Make sure that the compass is far away from the motors and any
wires that carry a substantial amount of current
- Check that the Frisbee is balanced (i.e., the center of mass is
at the center of the Frisbee)
Let us know if you need any additional components for mounting.
References
What to Hand In
The project checkpoint is due by Thursday, April 5th at 5:00pm.
- By this time, you should have parts 1-3 complete or near
completion (including testing).
- Code: check in your documented code to the subversion
repository by the deadline.
- Code review/demonstration: meet with the instructor or the TA to
discuss your current implementation and to show what you have
working. Plan on a 30 minute meeting. All group members should
be present.
All components of the project are due by Thursday, April 12th at 5:00pm.
Grading
Group grade distribution:
- 10%: Project checkpoint
- 30%: Project implementation
- 30%: Demonstration/presentation of working project (to either
of the TA or the instructor)
- 30%: Code documentation and group report
Grades for individuals will be based on the group grade, but weighted
by the assessed contributions of the group members.
fagg [[at]] cs.ou.edu
Last modified: Wed Apr 11 22:41:46 2012