PPOD-mini: 6-DOF Shaker
Team Members
- Ankur Bakshi (Biomedical Engineering, Class of 2009)
- Donald Redding (Mechanical Engineering, Class of 2009)
- Ben Tollberg (Mechanical Engineering, Class of 2009)
Introduction
The PPOD-mini is a miniaturized version of the Programmable Part-feeding Oscillatory Device {PPOD} found in the Laboratory for Intelligent Mechanical Systems (LIMS) at Northwestern. The PPOD-mini utilizes six speakers that act like actuators. The speakers are connected to a PVC plate via flexures of tygon and iron. In its current implementation, the phase of the speakers can be controlled independently, giving the device six degrees of freedom. The movement of objects placed on the PVC plate can be controlled by changing the phases of the speakers.
The PPOD mini measures about 12" x 12" x 8" and can be plugged into a wall outlet for power. It utilizes a switch to start the changing algorithm, a keypad for input to change the speaker phases, and an 16x2 line LCD for user interfacing and to output the speaker phases. A PIC 18f4520 provides the computing power. Each speaker is driven by an H-bridge, which is powered by a 10W power supply from Marlin Jones ([1]). The speakers are 1W, 16 ohm speakers from Jameco.
Operation
When first plugging in the PPOD-mini, the switch is triggered and the text on the LCD is garbled. By pressing the reset button on the PIC board or by pressing buttons on the keypad until clear text appears. To change the phase of a speaker, the switch must be pressed. Then following the instructions on the LCD, press the a number between one and six to select the speaker. Next, press a number between zero and five to choose a phase from 0-300 degrees in 60 degree increments. The increments can be changed by uploading a new program to the PIC. The speakers should then be running.
Mechanical Design
The PPOD consists of a circular plate mounted via flexible supports to six speakers. The six speakers are mounted in a circle on a base plate.
Speaker Mounts The speaker mounts are made of lightweight wood. The side of the mount which the speaker sits in is a 4" x 4" square. A circular hole is cut into the middle of this face to allow the speaker to fit in the mount. This face is elevated 30 degrees from the horizontal. There are two triangular sides on the speaker mount to achieve this angle. The height of the triangular sides is 2" and the base is 2*sqrt(3) (30-60-90 triangle).
Vibrating Plate Supports The flexible supports connecting the vibrating plate to the speakers is made of flexible plastic tubing. The tubing is 1/4" in diameter. To connect the tubing to the speakers, a hot glue gun was used. In order to ensure that the speakers are transmitting vibrations via the connections, a screw was placed in the middle of the tubing to increase the rigidity of the connection. This is done with one end of a 1/4" diameter screw stuck in to the tubing connected to the speaker. The other end of the 1/4"diameter screw is connected to another piece of tubing. This second piece of tubing goes to the vibrating plate.
Vibrating Plate The vibrating plate is circular and plastic. The diameter of this plate is 8". In order to connect the plate to the tubing, 6 holes are drilled equally spaced on a circle around the plate approximately half an inch from the plate's outer edge. Again 1/4" diameter screws are used to connect the tubing. The screws are fed down through the plate and into the second piece of tubing. Hot glue may be used as necessary on the plate around the screws to ensure the screws are held in place. Doing so ensures the vibration is transmitted from the speakers to the plate.
Circuit Diagram
To the right is the complete circuit schematic for our project. The +5V and GND for the L293D chips were supplied by the external power supply. The LCD was powered by the PIC +5V and GND in order to avoid some overheating issues that occurred when they were connected to the external supply. Each L293D chip controlled two speakers, with one full H-bridge for each speaker. The direction that voltage is applied to each speaker is controlled by the high or low signals that are output from the PIC to the H-bridges.
Code
The following is our main program code that was put on the PIC. The files flex_lcd.c and key.c must be included in the same folder to correctly operate the keypad and LCD. Our modified versions of those files are also included in this section.
Main Code
//Ankur Bakshi, Donnie Redding, Ben Tollberg //Group 23: PPOD-mini: 6-DOF Shaker #include <18f4520.h> #fuses HS,NOLVP,NOWDT #use delay (clock=40000000) #include "key.c" //keypad file #include "flex_lcd.c" //lcd file int32 frequency = 40; //Set speaker frequency in hertz, this is the one we used in an attempt to int ii; //maximize the acceleration of the speakers int32 halfperiod; int32 delays; int8 s_p[6]={0,0,0,0,0,0}; //Array that will hold the phase of each speaker char k; #INT_EXT void INT0isr() { //Interrupt routine is called when Pin RB0 goes from low to high //which occurs when the red button is pressed and released int s; int flag=1; char speaker; char phase; int i, j, temp; printf(lcd_putc,"\fChanging Phase"); delay_ms(10); printf(lcd_putc,"\n\fSpeaker Number:"); while(flag==1){ //Loop will repeat until acceptable input is pressed speaker = kbd_getc(); //Read speaker number from keypad if(speaker!=0) { //Determine speaker printf(lcd_putc,"%c",speaker); switch (speaker){ case '1': s=0; flag=0; //Exits loop if acceptable input is entered break; case '2': s=1; flag=0; break; case '3': s=2; flag=0; break; case '4': s=3; flag=0; break; case '5': s=4; flag=0; break; case '6': s=5; flag=0; break; default: printf(lcd_putc,"\n\fInvalid input."); delay_ms(10); printf(lcd_putc,"\n\nSpeaker Number:"); break; } } } flag=1; printf(lcd_putc,"\nEnter Phase:"); while(flag==1){ //Loop repeats until acceptable input is pressed phase = kbd_getc(); //Reads in phase from keypad if(phase!=0) { printf(lcd_putc,"%c",phase); switch (phase){ //Set phase array case '0': s_p[s]=0; flag=0; break; case '1': s_p[s]=1; flag=0; break; case '2': s_p[s]=2; flag=0; break; case '3': s_p[s]=3; flag=0; break; case '4': s_p[s]=4; flag=0; break; case '5': s_p[s]=5; flag=0; break; default: printf(lcd_putc,"\nInvalid input."); delay_ms(10); printf(lcd_putc,"\nEnter Phase:"); break; } } } } void main (){ enable_interrupts(INT_EXT); //Enables external interrupts enable_interrupts(GLOBAL); ext_int_edge(0, L_TO_H); //Sets external interrupt to trigger when RB0 goes from //low to high halfperiod = 500000/frequency; //Converts frequency in hertz to half of the period //in microseconds delays = halfperiod/3; //Sets delays to one-sixth of a full period, for a 60 degree //phase shift lcd_init(); kbd_init(); //initialize keypad printf(lcd_putc,"Starting...\n"); delay_ms(1000); printf(lcd_putc,"\n"); while(true) { //This function sets the speakers high or low based on if(s_p[0] == 0){ //the information saved in the phase array s_p. It checks output_high(PIN_A0); //to see which speakers are supposed to be changed at output_low(PIN_A1); //a certain time, waits for a time that equates to 60 } //degrees of phase shift and checks again. Changing which if(s_p[1] == 0){ //pin is high and which is low changes the direction of output_high(PIN_A2); //the voltage across the speakers. output_low(PIN_A3); } if(s_p[2] == 0){ output_high(PIN_A4); output_low(PIN_A5); } if(s_p[3] == 0){ output_high(PIN_C6); output_low(PIN_E0); } if(s_p[4] == 0){ output_high(PIN_E1); output_low(PIN_E2); } if(s_p[5] == 0){ output_high(PIN_C5); output_low(PIN_D0); } if(s_p[0] == 3){ output_low(PIN_A0); output_high(PIN_A1); } if(s_p[1] == 3){ output_low(PIN_A2); output_high(PIN_A3); } if(s_p[2] == 3){ output_low(PIN_A4); output_high(PIN_A5); } if(s_p[3] == 3){ output_low(PIN_C6); output_high(PIN_E0); } if(s_p[4] == 3){ output_low(PIN_E1); output_high(PIN_E2); } if(s_p[5] == 3){ output_low(PIN_C5); output_high(PIN_D0); } delay_us(delays); if(s_p[0] == 1){ output_high(PIN_A0); output_low(PIN_A1); } if(s_p[1] == 1){ output_high(PIN_A2); output_low(PIN_A3); } if(s_p[2] == 1){ output_high(PIN_A4); output_low(PIN_A5); } if(s_p[3] == 1){ output_high(PIN_C6); output_low(PIN_E0); } if(s_p[4] == 1){ output_high(PIN_E1); output_low(PIN_E2); } if(s_p[5] == 1){ output_high(PIN_C5); output_low(PIN_D0); } if(s_p[0] == 4){ output_low(PIN_A0); output_high(PIN_A1); } if(s_p[1] == 4){ output_low(PIN_A2); output_high(PIN_A3); } if(s_p[2] == 4){ output_low(PIN_A4); output_high(PIN_A5); } if(s_p[3] == 4){ output_low(PIN_C6); output_high(PIN_E0); } if(s_p[4] == 4){ output_low(PIN_E1); output_high(PIN_E2); } if(s_p[5] == 4){ output_low(PIN_C5); output_high(PIN_D0); } delay_us(delays); if(s_p[0] == 2){ output_high(PIN_A0); output_low(PIN_A1); } if(s_p[1] == 2){ output_high(PIN_A2); output_low(PIN_A3); } if(s_p[2] == 2){ output_high(PIN_A4); output_low(PIN_A5); } if(s_p[3] == 2){ output_high(PIN_C6); output_low(PIN_E0); } if(s_p[4] == 2){ output_high(PIN_E1); output_low(PIN_E2); } if(s_p[5] == 2){ output_high(PIN_C5); output_low(PIN_D0); } if(s_p[0] == 5){ output_low(PIN_A0); output_high(PIN_A1); } if(s_p[1] == 5){ output_low(PIN_A2); output_high(PIN_A3); } if(s_p[2] == 5){ output_low(PIN_A4); output_high(PIN_A5); } if(s_p[3] == 5){ output_low(PIN_C6); output_high(PIN_E0); } if(s_p[4] == 5){ output_low(PIN_E1); output_high(PIN_E2); } if(s_p[5] == 5){ output_low(PIN_C5); output_high(PIN_D0); } delay_us(delays); if(s_p[0] == 3){ output_high(PIN_A0); output_low(PIN_A1); } if(s_p[1] == 3){ output_high(PIN_A2); output_low(PIN_A3); } if(s_p[2] == 3){ output_high(PIN_A4); output_low(PIN_A5); } if(s_p[3] == 3){ output_high(PIN_C6); output_low(PIN_E0); } if(s_p[4] == 3){ output_high(PIN_E1); output_low(PIN_E2); } if(s_p[5] == 3){ output_high(PIN_C5); output_low(PIN_D0); } if(s_p[0] == 0){ output_low(PIN_A0); output_high(PIN_A1); } if(s_p[1] == 0){ output_low(PIN_A2); output_high(PIN_A3); } if(s_p[2] == 0){ output_low(PIN_A4); output_high(PIN_A5); } if(s_p[3] == 0){ output_low(PIN_C6); output_high(PIN_E0); } if(s_p[4] == 0){ output_low(PIN_E1); output_high(PIN_E2); } if(s_p[5] == 0){ output_low(PIN_C5); output_high(PIN_D0); } delay_us(delays); if(s_p[0] == 4){ output_high(PIN_A0); output_low(PIN_A1); } if(s_p[1] == 4){ output_high(PIN_A2); output_low(PIN_A3); } if(s_p[2] == 4){ output_high(PIN_A4); output_low(PIN_A5); } if(s_p[3] == 4){ output_high(PIN_C6); output_low(PIN_E0); } if(s_p[4] == 4){ output_high(PIN_E1); output_low(PIN_E2); } if(s_p[5] == 4){ output_high(PIN_C5); output_low(PIN_D0); } if(s_p[0] == 1){ output_low(PIN_A0); output_high(PIN_A1); } if(s_p[1] == 1){ output_low(PIN_A2); output_high(PIN_A3); } if(s_p[2] == 1){ output_low(PIN_A4); output_high(PIN_A5); } if(s_p[3] == 1){ output_low(PIN_C6); output_high(PIN_E0); } if(s_p[4] == 1){ output_low(PIN_E1); output_high(PIN_E2); } if(s_p[5] == 1){ output_low(PIN_C5); output_high(PIN_D0); } delay_us(delays); if(s_p[0] == 5){ output_high(PIN_A0); output_low(PIN_A1); } if(s_p[1] == 5){ output_high(PIN_A2); output_low(PIN_A3); } if(s_p[2] == 5){ output_high(PIN_A4); output_low(PIN_A5); } if(s_p[3] == 5){ output_high(PIN_C6); output_low(PIN_E0); } if(s_p[4] == 5){ output_high(PIN_E1); output_low(PIN_E2); } if(s_p[5] == 5){ output_high(PIN_C5); output_low(PIN_D0); } if(s_p[0] == 2){ output_low(PIN_A0); output_high(PIN_A1); } if(s_p[1] == 2){ output_low(PIN_A2); output_high(PIN_A3); } if(s_p[2] == 2){ output_low(PIN_A4); output_high(PIN_A5); } if(s_p[3] == 2){ output_low(PIN_C6); output_high(PIN_E0); } if(s_p[4] == 2){ output_low(PIN_E1); output_high(PIN_E2); } if(s_p[5] == 2){ output_low(PIN_C5); output_high(PIN_D0); } delay_us(delays); printf(lcd_putc,"\n\fPhases:%i%i%i%i%i%i\n",s_p[0],s_p[1],s_p[2],s_p[3],s_p[4],s_p[5]); } }
LCD Code
// flex_lcd.c // These pins were originally set for D0-D6, we changed them to fit // our needs. #define LCD_DB4 PIN_D1 #define LCD_DB5 PIN_D2 #define LCD_DB6 PIN_D3 #define LCD_DB7 PIN_D4 #define LCD_RS PIN_D5 #define LCD_RW PIN_D6 #define LCD_E PIN_D7 // If you only want a 6-pin interface to your LCD, then // connect the R/W pin on the LCD to ground, and comment // out the following line. #define USE_LCD_RW 1
//========================================
#define lcd_type 2 // 0=5x7, 1=5x10, 2=2 lines #define lcd_line_two 0x40 // LCD RAM address for the 2nd line int8 const LCD_INIT_STRING[4] = { 0x20 | (lcd_type << 2), // Func set: 4-bit, 2 lines, 5x8 dots 0xc, // Display on 1, // Clear display 6 // Increment cursor }; //------------------------------------- void lcd_send_nibble(int8 nibble) { // Note: !! converts an integer expression // to a boolean (1 or 0). output_bit(LCD_DB4, !!(nibble & 1)); output_bit(LCD_DB5, !!(nibble & 2)); output_bit(LCD_DB6, !!(nibble & 4)); output_bit(LCD_DB7, !!(nibble & 8)); delay_cycles(1); output_high(LCD_E); delay_us(2); output_low(LCD_E); } //----------------------------------- // This sub-routine is only called by lcd_read_byte(). // It's not a stand-alone routine. For example, the // R/W signal is set high by lcd_read_byte() before // this routine is called. #ifdef USE_LCD_RW int8 lcd_read_nibble(void) { int8 retval; // Create bit variables so that we can easily set // individual bits in the retval variable. #bit retval_0 = retval.0 #bit retval_1 = retval.1 #bit retval_2 = retval.2 #bit retval_3 = retval.3 retval = 0; output_high(LCD_E); delay_cycles(1); retval_0 = input(LCD_DB4); retval_1 = input(LCD_DB5); retval_2 = input(LCD_DB6); retval_3 = input(LCD_DB7); output_low(LCD_E); return(retval); } #endif //--------------------------------------- // Read a byte from the LCD and return it. #ifdef USE_LCD_RW int8 lcd_read_byte(void) { int8 low; int8 high; output_high(LCD_RW); delay_cycles(1); high = lcd_read_nibble(); low = lcd_read_nibble(); return( (high<<4) | low); } #endif //---------------------------------------- // Send a byte to the LCD. void lcd_send_byte(int8 address, int8 n) { output_low(LCD_RS); #ifdef USE_LCD_RW while(bit_test(lcd_read_byte(),7)) ; #else delay_us(60); #endif if(address) output_high(LCD_RS); else output_low(LCD_RS); delay_cycles(1); #ifdef USE_LCD_RW output_low(LCD_RW); delay_cycles(1); #endif output_low(LCD_E); lcd_send_nibble(n >> 4); lcd_send_nibble(n & 0xf); } //---------------------------- void lcd_init(void) { int8 i; output_low(LCD_RS); #ifdef USE_LCD_RW output_low(LCD_RW); #endif output_low(LCD_E); delay_ms(15); for(i=0 ;i < 3; i++) { lcd_send_nibble(0x03); delay_ms(5); } lcd_send_nibble(0x02); for(i=0; i < sizeof(LCD_INIT_STRING); i++) { lcd_send_byte(0, LCD_INIT_STRING[i]); // If the R/W signal is not used, then // the busy bit can't be polled. One of // the init commands takes longer than // the hard-coded delay of 60 us, so in // that case, lets just do a 5 ms delay // after all four of them. #ifndef USE_LCD_RW delay_ms(5); #endif } } //---------------------------- void lcd_gotoxy(int8 x, int8 y) { int8 address; if(y != 1) address = lcd_line_two; else address=0; address += x-1; lcd_send_byte(0, 0x80 | address); } //----------------------------- void lcd_putc(char c) { switch(c) { case '\f': lcd_send_byte(0,1); delay_ms(2); break; case '\n': lcd_gotoxy(1,2); break; case '\b': lcd_send_byte(0,0x10); break; default: lcd_send_byte(1,c); break; } } //------------------------------ #ifdef USE_LCD_RW char lcd_getc(int8 x, int8 y) { char value; lcd_gotoxy(x,y); // Wait until busy flag is low. while(bit_test(lcd_read_byte(),7)); output_high(LCD_RS); value = lcd_read_byte(); output_low(lcd_RS); return(value); } #endif
Keypad Code
Issues with Design
Future Steps
As mentioned in the previous section, the all the speakers do not vibrate with the same force. There are several ways to deal with this issue. First, a little experimentation may need to be done with the flexures. The flexures may be too stiff, preventing the diaphragm of the speakers from fully vibrating. The next thing to do is to check the level of the plate and the geometry of the speakers and insertion points. If the mass of the plate is unevenly distributed, some speakers may have to apply more force than others. Since the speakers will inevitably output unequal forces, amplitude control will most likely be necessary. This can be achieved by place a potentiometer between a line from the H-bridge and the speaker. Given the weak output of some speakers at 5V, reducing amplitude may not be sufficient. Raising the power source to 7V and getting higher power speakers (for example [2]) will resolve this issue.
The original PPOD used feedback from six accelerometers to independently control the amplitude of each of the speakers. One possible method to implement a simpler system would be to use one accelerometer in the middle of the plate. When all the speakers are set in phase, there should be Y acceleration without X acceleration. However, if not all of the speakers are firing equally, there will be X acceleration. Based on the X acceleration, the node that is misfiring can be calculated.
During testing, it was noted that holding down a speaker diaphragm (effectively turning it off) caused distinct movements. If the speakers are not strong enough to cause movement, adding the ability to turn of individual speakers may be necessary.