Difference between revisions of "Basketball"
(→Code) |
|||
Line 68: | Line 68: | ||
== Code == |
== Code == |
||
#include <18f4520.h> |
|||
#fuses HS,NOLVP,NOWDT,NOPROTECT |
|||
#use delay(clock=40000000) |
|||
#use rs232(baud=19200, UART1) |
|||
int16 EncCount = 9984; |
|||
float pGain =1.25; |
|||
float dGain =3; |
|||
signed int32 encoderTarget = 0; |
|||
signed int32 error = 0; |
|||
signed int32 lastError; |
|||
signed int32 encoderActual = 0; |
|||
signed int32 initialCount = 0; |
|||
signed int32 speed = 0; |
|||
signed int32 targetSpeed = 0; |
|||
signed int32 LeftError, LeftErrorLastTime=0, LeftIntegral, LeftDerivative, LeftTorque; |
|||
signed int32 derivative = 0; |
|||
signed int32 offset = 0; |
|||
int16 theta=0; |
|||
int16 thetaTarget = 0; |
|||
int16 encoderTargetTheta =0 ; |
|||
signed int32 thetaError = 0; |
|||
int16 upCount = 0, downCount = 0, lastUpCount = 0, lastDownCount = 0; |
|||
int launching = 0; |
|||
int32 milsecs = 0; |
|||
int rsCount = 0; |
|||
char c_0 = 0; |
|||
char c_1 = 0; |
|||
int16 distance =0 ; |
|||
float shotSpeed; |
|||
void LaunchBall(int16 speed, int16 angle); |
|||
void CalculateAndLaunch(float dist); |
|||
#INT_RDA |
|||
void INTRDAisr() { |
|||
if(kbhit()) { |
|||
if(rsCount == 0) c_0 = getc(); |
|||
else if(rsCount == 1) { |
|||
c_1 = getc(); |
|||
distance = (int16)c_1 + ((int16)c_0<<8); |
|||
CalculateAndLaunch((float)distance); |
|||
} |
|||
} |
|||
rsCount++; |
|||
if(rsCount >= 2) rsCount = 0; |
|||
} |
|||
#INT_TIMER2 // designates that this is the routine to call when timer3 overflows |
|||
void Timer2isr() { |
|||
milsecs++; |
|||
if ((milsecs & 7) == 0) { // servo routine every 4ms is plenty. 250x/sec |
|||
//update counters and encoder |
|||
upCount = get_timer0(); |
|||
encoderActual += upCount - lastUpCount; //NEW HERE (REMOVED (SIGNED INT16 CAST) |
|||
downCount = get_timer1(); |
|||
encoderActual -= downCount - lastDownCount; //NEW HERE SAME AS ABOVE |
|||
lastUpCount = upCount; |
|||
lastDownCount = downCount; |
|||
theta = (int16)(encoderActual - offset) % EncCount; |
|||
thetaError = (signed int32)thetaTarget- (signed int32)theta; |
|||
//speed controls whether it is in forward or reverse |
|||
if(thetaError > 0) speed = targetSpeed; |
|||
else if(thetaError < 0) speed = -targetSpeed; |
|||
encoderTarget += speed; //need to add accuracy to speed |
|||
encoderTargetTheta = (int16)(encoderTarget) % EncCount; |
|||
if(encoderTargetTheta > thetaTarget && speed > 0) { |
|||
encoderTarget -= encoderTargetTheta - thetaTarget; |
|||
targetSpeed = 0; |
|||
} else if(encoderTargetTheta < thetaTarget && speed < 0) { |
|||
encoderTarget += thetaTarget - encoderTargetTheta; |
|||
targetSpeed = 0; |
|||
} |
|||
encoderTargetTheta = (int16)(encoderTarget) % EncCount; |
|||
//do calculations for PD controller with new error |
|||
error = encoderTarget - (encoderActual - initialCount); |
|||
derivative = error - lastError; |
|||
lastError = error; |
|||
//At different speeds, need different gains, higher kps and kds for higher speeds ect... |
|||
if(speed <= 5) { |
|||
LeftTorque = 3*error + 2*derivative; |
|||
} else |
|||
if (speed <= 75) { |
|||
LeftTorque = 1.25 * error + 3 * derivative; // good luck w/o floats... |
|||
} else if (speed <= 100) { |
|||
LeftTorque = 2 * error + 6 * derivative; // good luck w/o floats... |
|||
} else { //speed > 100 |
|||
LeftTorque = pGain * error + dGain * derivative; // good luck w/o floats... |
|||
} |
|||
if(error > 0) { //going in the CCW direction, Left torque positive |
|||
output_low(PIN_C4); |
|||
} |
|||
else if(error < 0) { |
|||
//invert for PWM |
|||
LeftTorque = 624 + LeftTorque; |
|||
output_high(PIN_C4); |
|||
if(launching == 1 && thetaError < 0) { //used to tune when launching |
|||
launching = 0; |
|||
thetaTarget = 300; |
|||
targetSpeed = 10; |
|||
} |
|||
} |
|||
if(LeftTorque < 0) LeftTorque = 0; |
|||
if(LeftTorque > 624) LeftTorque = 624; |
|||
set_pwm1_duty(LeftTorque); |
|||
output_d(encoderActual); |
|||
} |
|||
} |
|||
void main() { |
|||
setup_timer_2(T2_DIV_BY_4, 78, 16); // clock at 16KHz, interrupt every 4*25nS * 4 * 78 * 16 = 0.5mS |
|||
setup_timer_0(RTCC_EXT_L_TO_H | RTCC_DIV_1); // default is divide by two! |
|||
setup_timer_1(T1_EXTERNAL | T1_DIV_BY_1); // default is divide by one |
|||
//setup offsets and intialize encoders |
|||
upCount = get_timer0(); |
|||
downCount = get_timer1(); |
|||
lastUpCount = upCount; |
|||
lastDownCount = downCount; |
|||
encoderActual += (upCount - downCount); |
|||
initialCount = encoderActual; |
|||
offset = encoderActual%EncCount; |
|||
if(encoderActual < 0) offset = -offset; |
|||
enable_interrupts(INT_TIMER2); |
|||
enable_interrupts(INT_RDA); |
|||
enable_interrupts(GLOBAL); |
|||
ext_int_edge(0, H_TO_L); // interrupt on INT0/RB0 pin, low to high transition |
|||
setup_ccp1(CCP_PWM); |
|||
set_pwm1_duty(0); |
|||
delay_us(10); |
|||
output_low(PIN_C4); |
|||
while(TRUE) { |
|||
delay_ms(5000); |
|||
} |
|||
} |
|||
void LaunchBall(int16 speed, int16 angle) { |
|||
targetSpeed = speed; |
|||
thetaTarget = angle; |
|||
launching=1; |
|||
} |
|||
void CalculateAndLaunch(float dist) { |
|||
dist = dist+30; //adjust for distance between ranger and motor |
|||
shotSpeed = -0.0012*(dist*dist) + 0.5884*dist + 89.678; |
|||
LaunchBall((int16)(shotSpeed/2), 3000); |
|||
} |
|||
== Results == |
== Results == |
||
In the end, the project ultimately succeeded -- baskets could be made. However, better tuning for the motor control and needs to be developed for more consistent results. |
In the end, the project ultimately succeeded -- baskets could be made. However, better tuning for the motor control and needs to be developed for more consistent results. |
Revision as of 23:34, 19 March 2009
<br=clear all>
Team Members
- John Rula (Mechanical Engineering, Class of 2009)
- Alex Wojcicki (Mechanical Engineering, Class of 2009)
- Meredith Chow (Electrical Engineering, Class of 2010)
Introduction
A throwing arm propelled by a Pittman DC brush motor is mounted on a turntable and throws the ball into the "hoop." The hoop is wrapped in reflective tape and an IR emitter, receiver pair is used to sense where the IR is reflected most (the hoop with highly reflective tape). An ultrasonic sensor then pings the hoop for the distance of the hoop. With this information, the arm is able to "make a basket."
Mechanical Design
The major mechanical components were machined out of clear acrylic using the laser cutter in the machine shop. Holes were machined and threaded as required. The base is a square (12" x 12") with threaded holes for attachment to the purchased turntable bearing. The turntable has threaded holes for attachment to the turntable bearing as well. Large holes were added to the turntable for screw access during assembly because once one of the parts is attached to the turntable, the screws would otherwise not be able to be tightened. The turntable features a rectangular cutout for the servo motor and threaded holes for mounting. There are additional through holes for mounting the sensor bracket and the motor supports.
Two motor supports were designed to accept the Pittman motor and hold it six inches above the turntable surface. Fasteners are inserted from below the turntable into threaded holes in the motor supports. Clamps were designed to thread onto the supports and secure the motor in place. The sensors (IR emitter/receiver pair and ultrasonic sensor) are mounted on a machined acrylic bracket that which is attached to the turntable again through fasteners inserted underneath the turntable.
The arm is also made of laser cut acrylic. It was designed with a close fit on the flat of the motor shaft and with a clamp for tightening using a threaded fastener. For manufacturing reasons, the ball holding portion of the arm was cut out of a separate piece of acrylic and secured to the end of the throwing arm.
The hoop can be anything with the highly reflective tape around it. It is suggested to have a circular profile for better performance detecting its center using the IR emitter/receiver.
Mechanical Components
Part | Part No. | Qty | Vendor | Price (Total) |
---|---|---|---|---|
Pittman Motor | GM8712 | 1 | Lab | - |
RC Servo Motor - Futaba | S3004 | 1 | Lab | - |
Acrylic .25" Thick, 24"X 24" | 8560K357 | 1 | McMaster | $39.63 |
Corrosion-Resistant Turntable | 6031K17 | 1 | McMaster | $2.42 |
Fasteners | - | 24 | Shop supply | - |
Rubber Feet | - | 4 | Lab | - |
Electrical Design
The IR pair, ultrasonic, and servo have a relatively simple circuit. In the diagram below, the IR pair, ultrasonic, and servo are controlled by the program on PIC1. The IR pair and ultrasonic sensor are stacked vertically and inserted into the fitted holes on the laser cut acrylic. Because of the close proximity of the IR pair, to prevent saturation of the IR receiver, the emitter was wrapped in electrical tape. This allowed for the detection of reflected IR rather than from the emitter right below the detector. The RC servo goes through a sweep looking for the hoop. It finds the hoop by storing the location of the highest IR detection. The reflective tape used in Design Challenge 2008 and 2009 was used to wrap the hoop and is used to assist in the finding of the hoop as it reflects more IR radiation than most common objects. After making its sweep, the servo returns to the position where the most IR was detected. The ultrasonic sensor will then ping the hoop to find the distance of the hoop.
The Pittman motor, H-Bridge, and encoder chip circuit is more complex. This circuit is run by PIC2. The information collected about the hoop location is communicated from PIC1 to PIC2. The Pittman is used to launch the ball. After the hoop is found, the arm attached to the Pittman launches the ball into the hoop. The shaft rotates forward and then reverses to its previous position. The device is then ready to find the hoop and launch the ball again.
The two pics communicated with a common ground and wire connecting RC6 and RC7 as shown in the diagram. The power supply was two 12 volt power supplies connected in series to give a total of 24 volts.
Electrical Components
Part | Part No. | Qty | Vendor | Price (Total) |
---|---|---|---|---|
PICs | PIC18F4520 | 2 | Lab | - |
Encoder | LS7083 | 1 | Lab | - |
H-Bridge | L298N | 1 | Lab | - |
Ping Ultrasonic Sensor | 28015 | 1 | Lab 5 supply | - |
IR Emitter | QED123 | 1 | Lab | - |
IR Phototransistor | LTR-4206E | 1 | Lab | - |
Diodes | 1N4148 | 4 | Lab | - |
Resistors | 47.5/150K | 2 | Lab | - |
Circuit Diagram
Code
- include <18f4520.h>
- fuses HS,NOLVP,NOWDT,NOPROTECT
- use delay(clock=40000000)
- use rs232(baud=19200, UART1)
int16 EncCount = 9984;
float pGain =1.25;
float dGain =3;
signed int32 encoderTarget = 0;
signed int32 error = 0;
signed int32 lastError;
signed int32 encoderActual = 0;
signed int32 initialCount = 0;
signed int32 speed = 0;
signed int32 targetSpeed = 0;
signed int32 LeftError, LeftErrorLastTime=0, LeftIntegral, LeftDerivative, LeftTorque;
signed int32 derivative = 0;
signed int32 offset = 0;
int16 theta=0;
int16 thetaTarget = 0;
int16 encoderTargetTheta =0 ;
signed int32 thetaError = 0;
int16 upCount = 0, downCount = 0, lastUpCount = 0, lastDownCount = 0;
int launching = 0;
int32 milsecs = 0;
int rsCount = 0;
char c_0 = 0;
char c_1 = 0;
int16 distance =0 ;
float shotSpeed;
void LaunchBall(int16 speed, int16 angle); void CalculateAndLaunch(float dist);
- INT_RDA
void INTRDAisr() {
if(kbhit()) { if(rsCount == 0) c_0 = getc(); else if(rsCount == 1) { c_1 = getc(); distance = (int16)c_1 + ((int16)c_0<<8); CalculateAndLaunch((float)distance); } } rsCount++; if(rsCount >= 2) rsCount = 0;
}
- INT_TIMER2 // designates that this is the routine to call when timer3 overflows
void Timer2isr() {
milsecs++; if ((milsecs & 7) == 0) { // servo routine every 4ms is plenty. 250x/sec
//update counters and encoder upCount = get_timer0(); encoderActual += upCount - lastUpCount; //NEW HERE (REMOVED (SIGNED INT16 CAST) downCount = get_timer1(); encoderActual -= downCount - lastDownCount; //NEW HERE SAME AS ABOVE
lastUpCount = upCount; lastDownCount = downCount; theta = (int16)(encoderActual - offset) % EncCount; thetaError = (signed int32)thetaTarget- (signed int32)theta; //speed controls whether it is in forward or reverse if(thetaError > 0) speed = targetSpeed; else if(thetaError < 0) speed = -targetSpeed;
encoderTarget += speed; //need to add accuracy to speed encoderTargetTheta = (int16)(encoderTarget) % EncCount; if(encoderTargetTheta > thetaTarget && speed > 0) { encoderTarget -= encoderTargetTheta - thetaTarget; targetSpeed = 0;
} else if(encoderTargetTheta < thetaTarget && speed < 0) { encoderTarget += thetaTarget - encoderTargetTheta; targetSpeed = 0; } encoderTargetTheta = (int16)(encoderTarget) % EncCount; //do calculations for PD controller with new error error = encoderTarget - (encoderActual - initialCount);
derivative = error - lastError; lastError = error; //At different speeds, need different gains, higher kps and kds for higher speeds ect... if(speed <= 5) { LeftTorque = 3*error + 2*derivative; } else if (speed <= 75) { LeftTorque = 1.25 * error + 3 * derivative; // good luck w/o floats... } else if (speed <= 100) { LeftTorque = 2 * error + 6 * derivative; // good luck w/o floats... } else { //speed > 100 LeftTorque = pGain * error + dGain * derivative; // good luck w/o floats... }
if(error > 0) { //going in the CCW direction, Left torque positive output_low(PIN_C4); } else if(error < 0) { //invert for PWM LeftTorque = 624 + LeftTorque; output_high(PIN_C4); if(launching == 1 && thetaError < 0) { //used to tune when launching launching = 0; thetaTarget = 300; targetSpeed = 10; } } if(LeftTorque < 0) LeftTorque = 0; if(LeftTorque > 624) LeftTorque = 624; set_pwm1_duty(LeftTorque);
output_d(encoderActual); }
}
void main() {
setup_timer_2(T2_DIV_BY_4, 78, 16); // clock at 16KHz, interrupt every 4*25nS * 4 * 78 * 16 = 0.5mS
setup_timer_0(RTCC_EXT_L_TO_H | RTCC_DIV_1); // default is divide by two! setup_timer_1(T1_EXTERNAL | T1_DIV_BY_1); // default is divide by one
//setup offsets and intialize encoders upCount = get_timer0(); downCount = get_timer1(); lastUpCount = upCount; lastDownCount = downCount;
encoderActual += (upCount - downCount);
initialCount = encoderActual;
offset = encoderActual%EncCount; if(encoderActual < 0) offset = -offset;
enable_interrupts(INT_TIMER2); enable_interrupts(INT_RDA); enable_interrupts(GLOBAL); ext_int_edge(0, H_TO_L); // interrupt on INT0/RB0 pin, low to high transition
setup_ccp1(CCP_PWM); set_pwm1_duty(0); delay_us(10);
output_low(PIN_C4);
while(TRUE) { delay_ms(5000); }
}
void LaunchBall(int16 speed, int16 angle) {
targetSpeed = speed; thetaTarget = angle; launching=1;
}
void CalculateAndLaunch(float dist) {
dist = dist+30; //adjust for distance between ranger and motor shotSpeed = -0.0012*(dist*dist) + 0.5884*dist + 89.678; LaunchBall((int16)(shotSpeed/2), 3000);
}
Results
In the end, the project ultimately succeeded -- baskets could be made. However, better tuning for the motor control and needs to be developed for more consistent results.
LINK TO VIDEO HERE
Problems Encountered
- Attempting to integrate multiple pics and circuits onto one circuit board did not give good results. Ultimately, we had to use separate boards for each pic. This could be due to the effect of motor noise on our control circuitry.
- Motor control was an intense programming effort -- almost a project in its own right. Trying to combine the motor control and calibrate our throwing distance was difficult. Designing a functioning throwing device along with getting the rest of the project implemented was difficult to complete with the given time constraints.
- Many components burned out. Fly back diodes were implemented however, we still went through quite a few H-bridges. Encoder chips also burned out. The PING sensor burned out as well. We are not sure of the cause of some of these part failures.
- The length of wires for the Encoder A and B should be limited. When using long wires running from the encoder, we were not able to get our encoder chip to function properly. Replacing them with shorter wires fixed the issue. Because the signal from the encoder is switching at such high speeds, the loss in the long wires played a significant effect resulting in a faulty encoder count. Larger diameter wires may be an alternate solution to this problem.
Notes
A few things we might change if we did it again:
- Simplify the motor control portion of the project. Trying to implement a speed control using the encoder count was difficult to implement. Simply using PWM to drive the motor at different speeds may have been sufficient.
- Test different wires for length and diameter for optimal performance for the Encoder A and B connections.
- Combine the PICs on one circuit board, solving the issues we encountered when attempting this.
- Perform a more detailed calibration, adjusting starting position, final launching position, and speed to get improved control over throwing distance.