//
// // NOTE NOTE NOTE NOTE - Showing this on a web page means that // occasional weird stuff happens even though I've coded it for a < PRE > // html type. Be sure to check the code carefully before trying to run it // You might also just want to download the zip version which is // at http://academic1.bellevue.edu/robots/ssc5apure.zip // // ssc5a.c rev 1.2 (changed timing just a little more) // includes DTR/CTS - // Meant to be used with CCS C, but can // probably be used with others with modification. // // This is the inverted version. To change to non-inverted // modify the ext_int_edge(L_TO_H) to ext_int_edge(H_TO_L) // in main and the three lines in the serial routine that // are commented. // // A shot at making an ssc controller using a 16f84 - 4 mhz clock only // // 1. interrupt driven // 2. handles 6 servos // 3. initial PWM is 1000 to 2000 us with 1500 as center (90 deg travel) // 4. to change to 180 degree travel, increase PWM to 500 to 2500 range // to do that, pull pin a2 high - else pull it low. // 5. resolution is 90/250 or 180/250 depending on which range // // Format for interrupt (three bytes expected) // // 0. use timeout, if timeout occurs (bad input), no changes // 1. first byte is a sync byte (255) and thrown away // 2. next byte is servo controller, number between 0 and 5, else return // 3. next byte is servo position - must be between 0 and 250 // 4. clear DTR // 5. update servo controller number in array with servo position // // note1: allowed bytes are 0 through 250, 251 - 255 reserved // but not necessarily used. // note2: the serial routine is straight from Peter Anderson's example // and that code really belongs to him. I modified it so it would // use non-inverted data. Visit Peter Anderson's web page at: // www.phanderson.com. The reason I used his code instead of the // built-in CCS serial routines was because CCS getch() will lock // up the PIC if it doesn't get a character. This forces a reset and // would be pretty ugly when used in a noisey environment. // Peter Anderson's routine has a timeout built in. // note3: all servos react differently. If you use 180 degree mode, be // sure the servo is not banging against stops! You may have to trim // back on the upper and lower limits to prevent this. If the // servo bangs against stops, it will overheat, break, etc. // I have some Hi-Tec 300s that use 20-230 as the range to get // 180 degrees. Either more or less will bang the stops. // note4: I could have made this a helluva lot more cryptic (and efficient) // but I decided to spell it out in case I wanted to understand it // six months from now. Therefore, you will see lots of code that // could be tweaked and made cleaner and faster. In most cases // I did it that way on purpose. You should play with the code // and make it way kewl. // note5: DTR is asserted with B7. If you don't wait until you've timed // the servos, there will be jitter when you go get the interrupt // Tie B7 to the CTS line of the data terminal (pin 8 on a 9 pin). // // Format for main loop // // 0. loop through array of values for the servo positions // 1. calculate values and send out appropriate pulses (one at a time) // 2. assert DTR to allow terminal to update if it feels like it // 3. delay 6 ms. total delay between updates will be about // 15 ms (allows for time taken to update other five servos and // lets you have several interrupts during each service cycle) // 4. drop DTR low to turn off terminal updates while setting positions // 5. goto 0 // #include < 16f84.h > #include < defs_f84.h > // get these from www.phanderson.com for free // (they are part of his example, called flash.zip) #fuses xt,nowdt,noput,noprotect #define RxData 0 // port B, pin 0 (external interrupt pin) #define maxServos 5 // 0-5 are possible servo numbers #use delay (clock=4000000) int get_ch(long t_wait); unsigned int position_array[6]={125,125,125,125,125,125}; // interrupt routine follows. For CCS compilers, the function that follows // the compiler directive is the interrupt function. It doesn't matter // what its name is. #int_ext int_routine() { // this has been done step by step. It could be a lot more // efficient - but this way you can read it and see what's what. // So far, it has worked fine this way. // // The general idea is to get 3 bytes on an interrupt. That will // equate to about 2.7 ms for each interrupt that occurs. // // The three bytes are:// 1. is thrown away // 2. is the servo number that will be updated // and must be between 0 and 3 inclusive. // 3. is a number between 0 and 250 which // will be multiplied by 4 or 8 by the main program. // If in 180 degree mode, then it is multipled by 8. // // Peter Anderson's code for getting a character from the // serial port includes a timeout value if a char is not // sent. CCS doesn't seem to have this feature so the // CCS serial input routines were not used. unsigned int servonum; // servo number passed in unsigned int serposition; // servo position passed in unsigned int syncByte; // syncByte - throw away syncByte=get_ch(1); servonum=get_ch(1); serposition=get_ch(1); // // stop any more servicing on this cycle // bit_clear(PORTB,7); // // sanity checks // if(servonum==255) return; if(servonum>maxServos) return; if(serposition>250) return; // // update position array // position_array[servonum]=serposition; } void main(void) { unsigned int x; unsigned long y; int skip=0; TRISA=0xFF; // define A as all inputs TRISB=0x01; // define B as all outputs except int port if(input(pin_a2)) skip=1; // set up for 90 or 180 degrees enable_interrupts(INT_EXT); // mandatory if you want externals ext_int_edge(L_TO_H); // This is for inverted data, use H_TO_L for non-inverted enable_interrupts(GLOBAL); // mandatory to start it all bit_clear(portb,7); // now start main loop if(!skip){ // // if here then 90 degree mode is selected. translates: 1000-2000 us // to get 90 degree mode, ground pin a2 else pull it high through 10k // while(1) { for(x=0;x<6;x++) // step through the array and send out the data to b1-b6 { y=position_array[x]; bit_set(PORTB,x+1); // // ok, before you grimace on these next lines, I tried some // simple math routines, but it became more efficient to do it // this way. If you find a way of cleaning this up (not to only // look good, but to work!) then fix this puppy up. // delay_ms(1); delay_us(y); delay_us(y); delay_us(y); delay_us(y); // this would be about 1000 us delay + 4 * position_array[x] in us bit_clear(PORTB,x+1); } enable_interrupts(GLOBAL); bit_set(PORTB,7); // enable DTR (CTS-pin 8 with a 9 pin RS232 to PC) delay_ms(4); // servo delay before next pulse - allows for // for a normal hobby servo - with all the calls // and overhead, this ends up as about 15 ms bit_clear(PORTB,7); delay_ms(2); // short delay before turning off interrupts (allows for slow terminals) disable_interrupts(GLOBAL); // it is bad to even get a noise interrupt } } // // if to here, then skip is selected. Which gets you 180 degrees // instead of 90 - translates: 500 to 2500 us pulse. // Note: be careful in this mode - many servos will bang // against stops if in 180 degree mode and 0 degrees or // 180 degrees is selected. Try 20 - 230 as min/max inputs // when you are first starting // while(1) { for(x=0;x<6;x++) // step through the array and send out the data to b1-b6 { y=position_array[x]; bit_set(PORTB,x+1); // // see comments in the above section about how the following // looks awful but works. // delay_us(230); delay_us(230); delay_us(y); delay_us(y); delay_us(y); delay_us(y); delay_us(y); delay_us(y); delay_us(y); delay_us(y); // this would be about 500 us delay + 8 * position_array[x] in us bit_clear(PORTB,x+1); } enable_interrupts(GLOBAL); bit_set(PORTB,7); // enable DTR (CTS-pin 8 with a 9 pin RS232 on a PC) delay_ms(4); // servo delay before next pulse - allows for // for a normal hobby servo - with all the calls // and overhead, this ends up as about 15 ms bit_clear(PORTB,7); delay_ms(2); // short delay before turning off interrupts (allows for slow terminals) disable_interrupts(GLOBAL); // shut off interrupts while starting servos } } // // RS232 input with timeouts. 9600 8N1 // This code is a section of code written by Peter Anderson // I modified it - any bugs are mine. // // Inverted version. // Non-inverted pulses start high and drop low // Inverted pulses start low and go high // // Meant to be a direct hookup to the PC (hook the // transmit line (pin 3 on a 9 pin DIN) through a // 47K resistor to the interrupt line. You can also // hook up to an MCU by monitoring the DTR line (a2) // until it goes high and then transmitting to B0 // int get_ch(long t_wait) // returns 0xff if no char recived within t_wait ms { int one_ms_loop, ser_loop, ser_data, ser_time; do { one_ms_loop = 100; // 100 times 10 usecs #asm SCAN_1: CLRWDT NOP NOP NOP NOP BTFSC PORTB, RxData // check serial in - for inverted data GOTO SERIN_1 // if start bit (change BTFSC to BTFSS for non-inverted) DECFSZ one_ms_loop, F GOTO SCAN_1 #endasm }while(--t_wait); return(0xff); #asm SERIN_1: SERIN_INV: MOVLW 8 MOVWF ser_loop CLRF ser_data MOVLW 51 // delay for 1.5 bits MOVWF ser_time // 3 + 51 * 3 = 156 usecs for 9600 SERIN_INV_1: DECFSZ ser_time, F GOTO SERIN_INV_1 SERIN_INV_2: BTFSS PORTB, RxData BSF STATUS, C // reverse for non-inverted (BSF, etc) BTFSC PORTB, RxData BCF STATUS, C // reverse for non-inverted (BCF, etc) RRF ser_data, F MOVLW 23 // one bit delay MOVWF ser_time // 10 + 23 * 4 = 102 usecs SERIN_INV_3: CLRWDT DECFSZ ser_time, F GOTO SERIN_INV_3 DECFSZ ser_loop, F // done? GOTO SERIN_INV_2 // get next bit MOVLW 10 MOVWF ser_time // wait for at least 1/2 bit SERIN_INV_4: CLRWDT DECFSZ ser_time, F GOTO SERIN_INV_4 #endasm return(ser_data); }