Difference between revisions of "Ball Balancing Challenge"
Line 78: | Line 78: | ||
==Software Design== |
==Software Design== |
||
The opening lines are necessary for establishing that we are using a PIC 18F4520, analog inputs, a clock, connecting with an ICD, and that we are using pin 36 instead of 16 for the second PWM output. This is also where we establish all of our variables and functions. |
|||
Source Code: |
|||
<pre> |
<pre> |
||
/* |
|||
touchscreen.c Mechatronics 333 Team 14 2/22/08 |
|||
Reads the touchscreen individually as two axes. Actuates motor based on either touchscreen or joystick control. |
|||
PIC powers either the Upper Left (Y-axis reading) or the Lower Right (X-axis reading) and grounds what it doesn't power. |
|||
Output reading is the value on that axis. |
|||
Two channels of PWM output, with the PWM2 output moved from pin 16 to pin 36 to avoid conflict with Timer1 input |
|||
Upper Left (UL) corner is PIN_C1, and Lower Left (LR) corner is always its inverse. |
|||
Upper Right (UR) corner is always high, and lower right (LL) is always ground, its inverse. |
|||
Peak voltage: Top reading voltage=4V and bottom reading voltage=1.2V |
|||
Min xread,yread values: 62,70 |
|||
Max xread,yread values: 189,177 |
|||
*/ |
|||
#include <18f4520.h> |
#include <18f4520.h> |
||
#DEVICE ADC=8 // set ADC to 8 bit accuracy |
#DEVICE ADC=8 // set ADC to 8 bit accuracy |
||
Line 109: | Line 95: | ||
void buzz(); |
void buzz(); |
||
</pre> |
|||
The main function starts with setting the correct initial values to the variables, and setting up the timers and analog ports. |
|||
<pre> |
|||
void main() { |
void main() { |
||
//Flash 3 times to let us know we're running |
|||
for (j=0;j<3;j++){ |
for (j=0;j<3;j++){ |
||
output_d(0b11111111); |
output_d(0b11111111); |
||
Line 140: | Line 131: | ||
set_pwm1_duty(39); |
set_pwm1_duty(39); |
||
set_pwm2_duty(39); |
set_pwm2_duty(39); |
||
</pre> |
|||
The following portion of code shows what happens when the game is continuously played. First the game waits for a coin, then it plays the game until the user strikes out, then it stops the motors from spinning and again waits for a coin. |
|||
<pre> |
|||
while (TRUE) { |
while (TRUE) { |
||
Line 154: | Line 150: | ||
//Gameplay begins. Controls go dead when strikes go too high; |
//Gameplay begins. Controls go dead when strikes go too high; |
||
while (coin==1) { |
while (coin==1) { |
||
</pre> |
|||
//TOUCHSCREEN READing, looks at the touchscreen readings |
|||
The touchscreen is read by having the PIC send alternating signals to 2 of the corners that are diagonal to each other while keeping the other two signals constant. This allows the touchscreen to read both axes each run through, and then display them in a manner where the x-axis reading corresponds to PIC LED outputs D0-D3, and the y-axis reading corresponds to outputs D4-D7. Although this gives visually a very poor resolution on the reading, it serves well enough as a debugging tool to make sure the reading is working. |
|||
<pre> |
|||
//Touchscreen reading, looks at the touchscreen readings |
|||
if (m==0) { |
if (m==0) { |
||
output_high(PIN_C1); //UL goes high, thus LR goes low; y-axis can be read |
output_high(PIN_C1); //UL goes high, thus LR goes low; y-axis can be read |
||
Line 169: | Line 169: | ||
m=0; |
m=0; |
||
} |
} |
||
//Check to see if the Ball is outide the allowed range of values to be "in the black" of the touchscreen |
|||
if ((yread< 90) | (yread >150) | (xread <90) | (xread > 150)) { |
|||
counter++; |
|||
if ((counter==3) && (strike==ball)) { |
|||
strike++; //If ball is "in the red" for a sufficiently long time, a strike is added. |
|||
} |
|||
} |
|||
else { |
|||
counter--; |
|||
//Check to see if the ball has returned from being "in the red." |
|||
if ((counter==-1) && (strike>ball)) { |
|||
ball++; |
|||
} |
|||
} |
|||
if ((counter==3) | (counter == -2)) counter=0; |
|||
if (strike==2) { |
|||
output_high(PIN_A3); |
|||
} |
|||
if (strike==3) { |
|||
output_high(PIN_A4); |
|||
} |
|||
if (strike==4) { |
|||
output_high(PIN_A5); |
|||
} |
|||
if (strike==5) { |
|||
output_high(PIN_E2); |
|||
} |
|||
if (strike==6) { |
|||
output_high(PIN_E0); |
|||
set_pwm1_duty(39); |
|||
set_pwm2_duty(39); |
|||
buzz(); |
|||
} |
|||
//Joystick Control |
|||
if (input(PIN_C7) != 0) { // toggle switch that switches between Joystick control system and Touchscreen control system |
|||
set_adc_channel(2); // there's only one ADC so select which input to connect to it; here pin AN1 |
|||
delay_us(10); // wait 10uS for ADC to settle to a newly selected input |
|||
read1 = read_adc(); //x-axis |
|||
if (read1<145 && read1>115) read1pwm=39; |
|||
else read1pwm=(read1*.3); |
|||
set_pwm1_duty(read1pwm); |
|||
set_adc_channel(1); // there's only one ADC so select which input to connect to it; here pin AN2 |
|||
delay_us(10); // wait 10uS for ADC to settle to a newly selected input |
|||
read2 = read_adc(); //y-axis |
|||
if (read2<145 && read2>115) read2pwm=39; |
|||
else read2pwm=(read2*.3); |
|||
set_pwm2_duty(read2pwm); |
|||
} |
|||
else { |
|||
output_d(0); |
|||
set_pwm1_duty(39); |
|||
set_pwm2_duty(39); |
|||
} |
|||
//LED readings for XY locations of the touchscreen |
//LED readings for XY locations of the touchscreen |
||
if (xread<75) { |
if (xread<75) { |
||
Line 293: | Line 231: | ||
output_high(PIN_D6); |
output_high(PIN_D6); |
||
output_high(PIN_D7); |
output_high(PIN_D7); |
||
} |
|||
</pre> |
|||
To find the "winning" ranges of the black area on the touchscreen, we measured the voltage output on each axis on each boundary line, and converted it from the 0V to 5V scale to the quantized 8 bit (0 to 255) scale. Our code then checks to see if the ball is not in this zone before assigning a strike to the user. The counter variable is to ensure that the ball is outside the range enough times in a row, and a low reading is not simply the result of inconsistencies in the touchscreen reading. |
|||
To stop the machine from simply running up the strikes while the ball is in the red, the code ensures that a "strike" cannot be assigned until a "ball" is assigned before hand. |
|||
For each strike, a corresponding LED is lit up, and upon the last strike, the buzz function is set off. |
|||
<pre> |
|||
//Check to see if the Ball is outide the allowed range of values to be "in the black" of the touchscreen |
|||
if ((yread< 90) | (yread >150) | (xread <90) | (xread > 150)) { |
|||
counter++; |
|||
if ((counter==3) && (strike==ball)) { |
|||
strike++; //If ball is "in the red" for a sufficiently long time, a strike is added. |
|||
} |
|||
} |
|||
else { |
|||
counter--; |
|||
//Check to see if the ball has returned from being "in the red." |
|||
if ((counter==-1) && (strike>ball)) { |
|||
ball++; |
|||
} |
|||
} |
|||
if ((counter==3) | (counter == -2)) counter=0; |
|||
if (strike==2) { |
|||
output_high(PIN_A3); |
|||
} |
|||
if (strike==3) { |
|||
output_high(PIN_A4); |
|||
} |
|||
if (strike==4) { |
|||
output_high(PIN_A5); |
|||
} |
|||
if (strike==5) { |
|||
output_high(PIN_E2); |
|||
set_pwm1_duty(39); |
|||
set_pwm2_duty(39); |
|||
buzz(); |
|||
} |
|||
</pre> |
|||
The PIC reads the joystick and quantizes the input from the analog 0V to 5V to the 8 bit quantized 0 to 255. It then scales that number to be between 0 and 78, the PWM extremes. |
|||
<pre> |
|||
//Joystick Control |
|||
if (input(PIN_C7) != 0) { // toggle switch that switches between Joystick control system and Touchscreen control system |
|||
set_adc_channel(2); // there's only one ADC so select which input to connect to it; here pin AN1 |
|||
delay_us(10); // wait 10uS for ADC to settle to a newly selected input |
|||
read1 = read_adc(); //x-axis |
|||
if (read1<145 && read1>115) read1pwm=39; |
|||
else read1pwm=(read1*.3); |
|||
set_pwm1_duty(read1pwm); |
|||
set_adc_channel(1); // there's only one ADC so select which input to connect to it; here pin AN2 |
|||
delay_us(10); // wait 10uS for ADC to settle to a newly selected input |
|||
read2 = read_adc(); //y-axis |
|||
if (read2<145 && read2>115) read2pwm=39; |
|||
else read2pwm=(read2*.3); |
|||
set_pwm2_duty(read2pwm); |
|||
} |
|||
else { |
|||
output_d(0); |
|||
set_pwm1_duty(39); |
|||
set_pwm2_duty(39); |
|||
} |
} |
||
} |
} |
||
} |
} |
||
} |
} |
||
</pre> |
|||
The buzz() function resets the counting variables to zero and sets the buzzer off by oscillating a PIC output into a basic buzzer speaker at an audible frequency. |
|||
<pre> |
|||
void buzz() { |
void buzz() { |
||
Line 303: | Line 317: | ||
strike=0; |
strike=0; |
||
ball=0; |
ball=0; |
||
counter = 0; |
|||
output_low(PIN_A3); |
output_low(PIN_A3); |
||
output_low(PIN_A4); |
output_low(PIN_A4); |
Revision as of 18:37, 20 March 2008
Team Members
- JJ Darling - Electrical Engineering Class of 2009
- Ben Schriesheim - Manufacturing Engineering Class of 2008
- Alex Leung - Biomedical Engineering Graduate Student
Overview
We built a horizontal circular platform that is actuated from underneath by three speakers placed in an equilateral triangle. The vibration of the platform causes a small object (e.g., an IC socket or a coin) to act as an hour "hand" on top of the platform. The object slides around the circular platform, impelled by friction forces due to the vibration. By placing the speakers at different phases and amplitudes, we got the objects to move to desired positions. Due to the nodes created by the speaker vibrations, the object will move back to the correct hour if it is moved away. Our project was given to us by Professor Colgate and was based upon the research of Professor Lynch.
Mechanical Design
The Vibratory clock consists of a wooden base, held up by adjustable legs, three speakers, and a circular platform. The following material were used to create the design:
Base:
- wood: 16" diameter, 0.75" thick
- holes: 6" diameter (3 total)
- adjustable legs: 3 rods: 3/8"-16; 4" long; 6 nuts total
Speakers:
- 3 Pyramid Power PW677X: 300W, 4 Ohms, 6.5" Chrome Subwoofer
- previously adapted speakers replaced center of speaker with attachments 3" in height and 1/2" hole on top
- 1/2" diameter PVC used to attach previous attachment to platform with a screw
- held in place by 2 set screws each
- centers placed 7.125" apart
Circular Platform
- PVC/Acrylic: 11.75" diameter, 0.25" thick
- machined on the LaserJet
- screw holes counter-sunk at 7.125" apart
- 2.875" above wooden base
- Silver Sharpie used for numbers
Reasoning for Geometry of Design
Equilateral Triangle: By having the speakers equidistant from each other, we were able to create symmetry in our design, which made programming the various nodes much easier. For example, we were able to use the same theory to find the node at 12 o'clock, 4 o'clock, and 8 o'clock. If the speakers were not placed equidistant from each other, two speakers placed at equal amplitudes and opposite phases would not have created a sink in the exact middle of the two speakers.
Adjustable Legs:
We needed to make the legs adjustable since it is essential that the platform be perfectly level in order for the objects placed on the platform to move in expected patterns. For example, if one leg is slightly shorter than the other two, the objects placed on the platform would tend to move towards that leg and we could no longer rely on symmetry to program the various nodes due to the effects of gravity.
Height of Platform:
Through our own experimentation and from Professor Lynch's research, we found that we needed to have the pivot point (i.e. the speaker diaphragm) significantly below the platform in order to create sinks. At one point in the design process, we attempted to create a different pivot point by replacing the PVC with 1/2" outer diameter, 1/4" inner diameter Tygon 2001 tubing that only was able to bend in one point since it had stand-offs and screws inside the tube. However, this replacement in a sense created two pivot points (the speaker diaphragm and the Tygon), causing the platform to only create sources and not sinks. In addition, the different height of the pivot point may have also led to the plate acting differently.
Electrical Design
The Electical Design was fairly simple for this project. The inputs to the PIC were the joystick, coin slot, and the touchscreen. The outputs were the motors and the LED "strike" array. The touchscreen was also an output as it was controlled by the PIC.
The user would control the joystick that was simply two potentiometers that would output a value between 0V and 5V, corresponding to both the x-axis and the y-axis.
Component List:
Part | Part No. | Qty | Vendor | Price (Total) |
---|---|---|---|---|
Microchip 8-bit PIC Microcontroller (U1) | PIC18F4520 | 1 | N/A | N/A |
Quadruple Half-H Drivers | L293D | 2 | Digi-Key | $1.93 |
Hex Inverter | SN74HC04 | 1 | Digi-Key | $0.47 |
Touchscreen | BER237-ND | 1 | Digi-Key | $62.00 |
Joystick | N/A | 1 | N/A | N/A |
Circuit Diagram:
Software Design
The opening lines are necessary for establishing that we are using a PIC 18F4520, analog inputs, a clock, connecting with an ICD, and that we are using pin 36 instead of 16 for the second PWM output. This is also where we establish all of our variables and functions.
#include <18f4520.h> #DEVICE ADC=8 // set ADC to 8 bit accuracy #fuses HS,NOLVP,NOWDT,NOPROTECT, CCP2B3 // CCP2B3 moves PWM2 output to pin 36 (RB3) rather than pin 16 (RC1) #device icd=true #use delay(clock=20000000) int yread,xread,m,j=0; int read1pwm,read2pwm=0; int read1,read2=0; int coin,strike,ball=0; signed int16 counter=0; void buzz();
The main function starts with setting the correct initial values to the variables, and setting up the timers and analog ports.
void main() { //Flash 3 times to let us know we're running for (j=0;j<3;j++){ output_d(0b11111111); delay_ms(250); output_d(0); delay_ms(250); } coin=0; strike=0; ball=0; counter=0; output_high(PIN_C1); //Start with UL high, LR low ***THESE WILL CHANGE*** output_high(PIN_C0); //This is the pin for UR. It will be run through an inverter for LL //the bottom left corner. ***THESE ARE PERMANENT*** setup_adc_ports(AN0_TO_AN2); // Enable analog inputs; choices run from just AN0, up to AN0_TO_AN11 setup_adc(ADC_CLOCK_INTERNAL); // the range selected has to start with AN0 setup_timer_2(T2_DIV_BY_4, 77, 16); // clock at 16KHz, interrupt every 4*50nS * 4 * (155+1) * 16 = 2.00mS enable_interrupts(INT_TIMER2); enable_interrupts(GLOBAL); setup_ccp1(CCP_PWM); // PWM output on CCP1/RC2, pin 17 this goes to y-axis motor setup_ccp2(CCP_PWM); // PWM output on CCP2/RB3, pin 36 this goes to x-axis motor set_pwm1_duty(39); set_pwm2_duty(39);
The following portion of code shows what happens when the game is continuously played. First the game waits for a coin, then it plays the game until the user strikes out, then it stops the motors from spinning and again waits for a coin.
while (TRUE) { set_pwm1_duty(39); set_pwm2_duty(39); //Wait for coin to be inserted while (coin==0) { if (input(PIN_E1)) coin=1; else delay_ms(100); } //Gameplay begins. Controls go dead when strikes go too high; while (coin==1) {
The touchscreen is read by having the PIC send alternating signals to 2 of the corners that are diagonal to each other while keeping the other two signals constant. This allows the touchscreen to read both axes each run through, and then display them in a manner where the x-axis reading corresponds to PIC LED outputs D0-D3, and the y-axis reading corresponds to outputs D4-D7. Although this gives visually a very poor resolution on the reading, it serves well enough as a debugging tool to make sure the reading is working.
//Touchscreen reading, looks at the touchscreen readings if (m==0) { output_high(PIN_C1); //UL goes high, thus LR goes low; y-axis can be read set_adc_channel(0); delay_us(10); yread = read_adc(); //Read y axis m++; } else { output_low(PIN_C1); //UL goes low, thus LR goes high; x-axis can be read set_adc_channel(0); delay_us(10); xread = read_adc(); //Read x axis m=0; } //LED readings for XY locations of the touchscreen if (xread<75) { output_low(PIN_D0); output_low(PIN_D1); output_low(PIN_D2); output_low(PIN_D3); } else if (xread<110) { output_high(PIN_D0); output_low(PIN_D1); output_low(PIN_D2); output_low(PIN_D3); } else if (xread<145) { output_high(PIN_D0); output_high(PIN_D1); output_low(PIN_D2); output_low(PIN_D3); } else if (xread<180) { output_high(PIN_D0); output_high(PIN_D1); output_high(PIN_D2); output_low(PIN_D3); } else { output_high(PIN_D0); output_high(PIN_D1); output_high(PIN_D2); output_high(PIN_D3); } if (yread<75) { output_low(PIN_D4); output_low(PIN_D5); output_low(PIN_D6); output_low(PIN_D7); } else if (yread<110) { output_high(PIN_D4); output_low(PIN_D5); output_low(PIN_D6); output_low(PIN_D7); } else if (yread<145) { output_high(PIN_D4); output_high(PIN_D5); output_low(PIN_D6); output_low(PIN_D7); } else if (yread<180) { output_high(PIN_D4); output_high(PIN_D5); output_high(PIN_D6); output_low(PIN_D7); } else { output_high(PIN_D4); output_high(PIN_D5); output_high(PIN_D6); output_high(PIN_D7); }
To find the "winning" ranges of the black area on the touchscreen, we measured the voltage output on each axis on each boundary line, and converted it from the 0V to 5V scale to the quantized 8 bit (0 to 255) scale. Our code then checks to see if the ball is not in this zone before assigning a strike to the user. The counter variable is to ensure that the ball is outside the range enough times in a row, and a low reading is not simply the result of inconsistencies in the touchscreen reading. To stop the machine from simply running up the strikes while the ball is in the red, the code ensures that a "strike" cannot be assigned until a "ball" is assigned before hand.
For each strike, a corresponding LED is lit up, and upon the last strike, the buzz function is set off.
//Check to see if the Ball is outide the allowed range of values to be "in the black" of the touchscreen if ((yread< 90) | (yread >150) | (xread <90) | (xread > 150)) { counter++; if ((counter==3) && (strike==ball)) { strike++; //If ball is "in the red" for a sufficiently long time, a strike is added. } } else { counter--; //Check to see if the ball has returned from being "in the red." if ((counter==-1) && (strike>ball)) { ball++; } } if ((counter==3) | (counter == -2)) counter=0; if (strike==2) { output_high(PIN_A3); } if (strike==3) { output_high(PIN_A4); } if (strike==4) { output_high(PIN_A5); } if (strike==5) { output_high(PIN_E2); set_pwm1_duty(39); set_pwm2_duty(39); buzz(); }
The PIC reads the joystick and quantizes the input from the analog 0V to 5V to the 8 bit quantized 0 to 255. It then scales that number to be between 0 and 78, the PWM extremes.
//Joystick Control if (input(PIN_C7) != 0) { // toggle switch that switches between Joystick control system and Touchscreen control system set_adc_channel(2); // there's only one ADC so select which input to connect to it; here pin AN1 delay_us(10); // wait 10uS for ADC to settle to a newly selected input read1 = read_adc(); //x-axis if (read1<145 && read1>115) read1pwm=39; else read1pwm=(read1*.3); set_pwm1_duty(read1pwm); set_adc_channel(1); // there's only one ADC so select which input to connect to it; here pin AN2 delay_us(10); // wait 10uS for ADC to settle to a newly selected input read2 = read_adc(); //y-axis if (read2<145 && read2>115) read2pwm=39; else read2pwm=(read2*.3); set_pwm2_duty(read2pwm); } else { output_d(0); set_pwm1_duty(39); set_pwm2_duty(39); } } } }
The buzz() function resets the counting variables to zero and sets the buzzer off by oscillating a PIC output into a basic buzzer speaker at an audible frequency.
void buzz() { coin=0; strike=0; ball=0; counter = 0; output_low(PIN_A3); output_low(PIN_A4); output_low(PIN_A5); output_low(PIN_E2); output_low(PIN_E0); while (input(PIN_C6)==0) { output_high(PIN_C4); delay_us(100); output_low(PIN_C4); delay_us(100); } }
Results
We were able to get our project working so that the plate moves an object placed anywhere on the plate to the correct time and then moves the objects around the clock face, acting as the hour "hand" of a clock. However, we increased the speed so that it takes about 2 minutes for objects placed on the clock to move around so that it is easy to demonstrate. Check out the working Vibratory Clock here.
Reflections
Useful Resources
Vibration-Induced Frictional Force Fields for Part Manipulation