Ball Balancing Challenge
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