//
//
// 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);
}