Ball Balancing Challenge
Team Members
- JJ Darling - Electrical Engineering Class of 2009
- Ben Schriesheim - Manufacturing Engineering Class of 2008
- Alex Leung - Biomedical Engineering MS 2009
Overview
Based off an original idea of ball juggling, the purpose of our project was to test and gauge the capability of using a resistive touchscreen with a PIC Microcontroller. Our original projected means for testing the touchscreen for our project was to attempt to design and implement a system that would keep a round metal ball in the center of the touchscreen. Using the touchscreen's analog outputs of position, we would design a control system that would use a two-axis actuator system to move the touchscreen up and down depending on disturbances to keep the metal ball in the same place. Automatic control and manual control elements were to be implemented with the manual control element.
However, due to complications with our touchscreen readings, our group decided to focus on the essentials of our project and have it focus on both the touchscreen capabilities and try to make the project as interactive as possible. A joystick (made from two potentiometers) was used to control the two motors which would lift the table up in either axis. The game begins with a coin machine which, when a coin is slided in, would set off a contact and initiate the motor control. The design of our game was so that the player would attempt to keep the ball in a restricted region in the middle of the touchscreen, which was marked in black. The outside region of the touchscreen, marked in red, would set off LED's that would go off if the player stayed in the red region for an extended time. The player would lose after 4 LED's light up and the buzzer goes off. The player can reset the game but the motor control would cease to function and a coin is needed to reactivate the game again.
Mechanical Design
Our team weighed several alternatives for a mechanical system that could turn the screen in two axes. Potential solutions included a gimble system with a double-frame design and a pully-operated system that would lift up on two sides of the screen. Eventually, we decided on a system that was theoretically less precise, but much simpler and requiring less power (since one motor does not need to lift another). The system worked well under joystick control, but is only reliable for small tilting angles.
A sheet metal frame was built around the frame for protection, supported at its center of gravity by a pillar attached to the wooden base. The frame can freely tilt in two directions, allowing all possible ball movements. By raising and lowering two adjacent sides of the frame, the touch screen can be tilted in two directions. This motion is provided by two Falhauber motors mounted underneath the screen. The output shaft of the motors' gear boxes are attached to a threaded rod by couplers that we machined for the purpose. The couplers are attached to the motor shaft by a radial set screw and to the threaded rods by locking nuts. Riding on these rods are two threaded aluminum pieces, which are attached to the bottom of the frame via flexible tubing and epoxy. As the angle of the screen changes in one direction, the location of the attachment of the other motor moves slightly relative to the base of the motor. The flexible coupling allows for this motion, while providing vertical stability and transferring the up and down motion of the threaded pieces to the sides of the frame.
Provided that the tilt angle remains small, its value can be approximated by the inverse tangent of the vertical motion of the threaded rod divided by 9cm (the horizontal distance from the tubing connection to the center of the frame). By controlling the speed of the motors via pulse width modulation, the tilt angles of the touch screen can be changed at a variety of speeds.
Other features include adjustable legs for stabilizing the wooden base on an uneven surface, and an upper frame above the touchscreen that prevents the ball from rolling onto the non-sensing regions at the periphery of the touchscreen. The mechanical design for this project was the simplest and most inexpensive option for this application; if a greater level of precision were required, a more complicated mechanism may have been necessary.
The coin slot box was simply a ramp that would lead the dropped coin onto a conducting plate. The plate was supported by a weak spring that would bend under the weight of the coin to briefly touch another conducting plate below it before the coin rolled off. The brief contact between the two plates would send a high voltage to the PIC microcontroller.
Electrical Design
The Electrical 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. This went to one of the PIC's analog inputs. The coin would simply fall on a simple switch, connecting 5V to the PIC. The touchscreen input looks like a square wave, except the two values being received correspond to the voltage from each axis. Separating these two values is done in software.
Toggle switches were added to allow easy powering on and off of our system. Also, a switch for automatic and manual control was implemented which was designed to have the purpose of switching the design from joystick control to automatic system control (self-adjusting touchscreen). The reset button was implemented to end the game at any time and end the main loop so that the motors would be inactive.
The PIC output to the motors corresponds directly to the joystick input. The PIC output goes both to the H-bridge and an inverter for each motor, so that the motor sees not a PWM signal and ground, but a PWM signal with its inverse PWM signal. As a result of this, both poles of the motor see the same thing when the high and low pulses of the signal are equal. The motors turn one way proportionally to the joystick position when the PWM has more time high than low (when that joystick potentiometer is sending a higher signal)and the other way when the PWM is more low than high.
The PIC output to the touchscreen is very simple. Although we had it go through an H-bridge to amplify the current, this is probably not necessary. Because the diagonally opposite corners of the touchscreen always have an opposite voltage going into them, we just ran the signal through an inverter so that we could control all four corners with only two PIC outputs.
Information about the specifics, implementation and code that was used for the reading of the resistive touchscreen can be found here.
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
This project was programmed using CCS C on a PIC 18F4520. See the page on 4520 Board use for more information on using the boards we used.
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); while (input(PIN_C6)==0) { output_high(PIN_C4); delay_us(100); output_low(PIN_C4); delay_us(100); } }