/* Needed for v2.1 1 Cure jitter on servo outputs 2 preserve settings across a reset (without power loss) */ #include #include __CONFIG(UNPROTECT & PWRTDIS & WDTDIS & XT); #define _XTAL_FREQ 4000000 volatile unsigned char power_up; /* try to work out why we reset */ typedef union { volatile unsigned char ALL; struct { unsigned B0:1; unsigned B1:1; unsigned B2:1; unsigned B3:1; unsigned B4:1; unsigned B5:1; unsigned B6:1; unsigned B7:1; }; } port_t; port_t port_d; /* mirror of PORTD bits */ port_t port_b; volatile unsigned int rb7_changes; /* number of changes on PORTB 7 */ volatile unsigned int rb6_changes; volatile unsigned long tick_1s; typedef union { volatile unsigned char ALL; struct { unsigned D2S:1; /* 1 or more char to send */ unsigned FULL:1; /* buffer empty */ }; } txflag_bits_t; txflag_bits_t TxFlags; typedef union { volatile unsigned long ALL; struct { unsigned char low; /* holds TMR1L value */ unsigned char high; /* holds TMR1H value */ unsigned int top16; /* incremented by TMR1 overflow interrupt */ }; } tmr1_t; tmr1_t timer1; unsigned long timer1_1sec, timer1_prev; unsigned int sched_on[8]; /* on times for PORT D bits */ unsigned int sched_off[8]; /* off times for PORT D bits */ typedef union { volatile unsigned char ALL; struct { unsigned ms10:1; unsigned ms20:1; unsigned ms40:1; unsigned ms80:1; unsigned ms160:1; unsigned ms320:1; unsigned ms640:1; unsigned ms1280:1; }; } timer_10ms_t; timer_10ms_t tick_10ms; typedef union { volatile unsigned char ALL; struct { unsigned NEW:1; unsigned EOL:1; unsigned P2:1; unsigned P3:1; unsigned P4:1; unsigned P5:1; unsigned P6:1; unsigned P7:1; }; } rx_stat_t; rx_stat_t RxFlags; volatile char InByte; volatile char RecSkips; volatile char TxSkips; volatile char RxChar; volatile char TxChar; volatile unsigned int interval_val; /* interval timer for RA4 */ volatile unsigned int interval_tmr; typedef union { unsigned char ; struct { unsigned LED:1; unsigned SERVO:1; unsigned P2:1; unsigned SQUARE:1; unsigned INTVL:1; unsigned READYTOSEND:1; unsigned SENDNEVER:1; unsigned SENDNOW:1; }; } timer_bits_t; timer_bits_t timer_bits; volatile unsigned int tx_time; /* time (in ticks) between outputs */ volatile unsigned int tx_delay; /* ticks remaining to next output */ #define TxBUF_SIZE 32 #define TxBUF_MASK (TxBUF_SIZE-1) volatile char TxBuf[TxBUF_SIZE]; #define RxBUF_SIZE 16 #define RxBUF_MASK (RxBUF_SIZE-1) volatile char RxBuf[RxBUF_SIZE]; volatile char RxIPtr; volatile char RxOPtr; volatile char TxIPtr; volatile char TxOPtr; unsigned char servo1_val, servo2_val; /* position data for servos, 0 - 100 */ /******************************************************************************/ /* general purpose interrupt handler */ /* Comes in three sections */ /* Timer1 overflow, to increase the width of the frequency count to 32 bits */ /* Timer2 interrupt every 10mSec. This drives all the time-sensitive features */ /* Serial I-O. Actually 2 interrupts, one for transmitted data and one for received */ void interrupt isr(void) { char c; unsigned char i; if(TMR1IF) { TMR1IF = 0; timer1.top16++; } /* TMR2 is the main 10mSec interrupt that is used for timing */ /* and scheduling the time-critical operations, such as sampling */ /* TMR1's counts every 10mSec and every 1 second */ if(TMR2IF) { tick_10ms.ALL++; if(timer_bits.SQUARE) RC3 = tick_10ms.ms10; /* 50Hz square wave */ /* check if any of the RB7 :: RB4 pins have changed in the last tick */ /* update the counter if so */ if(port_b.B7 != RB7) rb7_changes++; if(port_b.B6 != RB6) rb6_changes++; port_b.ALL = PORTB; /* processing every 1 second */ if(tick_10ms.ALL > 99) { tick_10ms.ALL = 0; tick_1s++; /* Read full value of Timer1 (incl. overflows) every 1 second */ TMR1ON = 0; timer1.low = TMR1L; timer1.high = TMR1H; TMR1ON = 1; /* here tiemr1.ALL contains the 32 bit count of Timer1 inputs */ timer1_1sec = timer1.ALL - timer1_prev; timer1_prev = timer1.ALL; /* finally update all the on / off times, if one hits zero, execute it */ for(i=0; i < 8; i++) { if(sched_on[i]) { sched_on[i]--; if(sched_on[i] == 0) port_d.ALL |= (1 << i); } if(sched_off[i]) { sched_off[i]--; if(sched_off[i] == 0) port_d.ALL &= ~(1 << i); } } } if (tick_10ms.ms10) { timer_bits.SERVO = 1; /* initiate servo pulses every 20mS */ } if(tick_10ms.ms160) { RC1 = 0; } else { /* toggle LED every 160mSec */ RC1 = 1; } if(RA4) { interval_tmr++; /* bump the interval timer while RA4 is high */ timer_bits.INTVL = 1; } else { if(timer_bits.INTVL) { /* was high last time round, low now */ interval_val = interval_tmr; interval_tmr = 0; timer_bits.INTVL = 0; } } /* now decrement the timer to say when to send the next line of data */ if(tx_delay > 0) { tx_delay--; if(tx_delay == 0) { if(timer_bits.SENDNOW) tx_delay++; /* hold off until last line finished */ else timer_bits.READYTOSEND = 1; tx_delay = tx_time; /* reload the timer */ } } TMR2IF = 0; /* typ. service time 90uS, 550us each second */ } /********** end of TMR2 interrupt **********/ /* This code handle RS232 I-O, it processes inteerupts when the */ /* the transmit buffer has been emtied (by sending a char) */ /* and whent he receive buffer reads is loaded with an incoming char */ if(TXIF) { /* transmit buffer empty interrupt */ if(TxFlags.D2S) { TXREG = TxBuf[TxOPtr++]; TxOPtr = TxOPtr & TxBUF_MASK; TxFlags.FULL = 0; if(TxOPtr == TxIPtr) { /* buffer empty, turn off interrrupts */ TxFlags.D2S = 0; // TXIE = 0; } } else { /* no data ready */ TXIE = 0; } } if(RCIF) { /* received character interrupt */ /* Note: we only want to fill the */ /* input buffer if this message is */ /* addressed to us. */ c = RCREG; RxBuf[RxIPtr++] = c; RxFlags.NEW = 1; if(c == '\n') RxFlags.EOL = 1; RxIPtr = RxIPtr & RxBUF_MASK; } } /* routine to wait a certain number of "ticks" of 4uSec each */ /* (4uS from the timer and 1uS for the loop */ /* this performs real-time waits, based on Timer0 heardware */ /* rather than software delay loops which are subject to elongation */ /* when an interrupt gets serviceed during the loop */ void rt_ticks(unsigned char t) { if(t < 6) return; /* calling overheads are 25 uS */ TMR0 = 0; while (TMR0 < t) { asm("nop"); /* necessary to stop the loop being optimised out */ } } /* routine to read the ADC, use oversampling */ /* to improve the resolution of the ADC from 8 to 12 bits */ int read_adc(unsigned char chn) { unsigned int i; unsigned int res; ADCON0 = 0x40 | ((chn & 0x7) << 3) | ADON; /* 8Tosc (Tad = 2uS) , set channel, turn ADC on */ /* Here we oversample the same channel 256 times to improve ADC resolution */ /* Note: resolution, not accuracy */ /* At the end, drop the 4 LSBs to return a 12 bit result */ for (res = 0, i = 0; i < 256; i++) { ADIF = 0; GODONE = 1; /* trigger conversion */ while(!ADIF) { /* wait for conversion to complete */ asm("nop"); /* to prevent the loop being optimised out */ } //#asm //adcloop: // btfss _PIR1,6 // goto adcloop // movf _ADRES, w // addwf _read_adc_res, f // skipnc // incf _read_adc_res+1, f //#endasm res += ADRES; /* read 8-bit ADC result and accumulate */ /* wait 2Tad (4uSec) before next conversion */ /* this is accounted for in the for() loop time */ } return(res >> 4); /* return 12-bit result (13.5mSec @ 4MHz) */ } /* Enqueue data in the transmit buffer to send down the serial line */ /* This routine adds one character at a time to the 32 byte buffer */ /* (the size is constrained b the compiler and RAM limitations of the */ /* processor). When a character has been added, the "new data" flag is */ /* set to tell the interrupt routine to restart sending data. */ /* Note: data can be added to the buffer faster than the interrupt */ /* routine can transmit it */ void SendChar(char c) { if(TxFlags.FULL) { /* Tx buffer full */ rt_ticks(250); /* wait 1.5 times the time needed to */ rt_ticks(250); /* send 1 char, should be enough */ rt_ticks(250); // rt_ticks(250); // rt_ticks(250); // rt_ticks(250); // rt_ticks(250); // rt_ticks(250); } TXIE = 0; /* disable interrupts to avoid */ /* a race condition while we diddle */ /* the queue pointers */ TxBuf[TxIPtr++] = c; TxIPtr = TxIPtr & TxBUF_MASK; TxFlags.D2S = 1; /* new data to send */ if(TxOPtr == TxIPtr) { TxFlags.FULL = 1; } /* buffer now full */ TXIE = 1; /* safe to restart them now */ } /* Routine to send a string */ /* Assume normal "C" style string, terminated by a NULL char */ void SendStr(const char *p) { while(*p) { if(*p == '\n') { SendChar(13); SendChar(10); p++; } else { SendChar(*p++); } } } /***********************************************************************/ /* Type conversion routines */ /***********************************************************************/ /* convert two ASCII characers in to an 8-bit value */ unsigned char hex2num(char h1, char h2) { unsigned char num; char h; h = h1 - '0'; if(h > 9) { h -= 7; } num = h * 16; h = h2 - '0'; if(h > 9) { h -= 7; } num += (h & 0xf); return(num); } char to_hex( unsigned char n) { n = n & 0xf; if(n > 9) { n += 7; } n += '0'; return(n); } /* convert an 8 bit value (less than 100) to 2 ASCII/decimal characters */ void pr_ch99(unsigned char c) { char b[2]; SendChar('0' + (c / 10)); SendChar('0' + (c % 10)); } /* print a hex byte as two characters */ void pr_hex(unsigned char i) { unsigned char b; b = i >> 4; if(b > 9) { b += 7; } SendChar('0'+b); b = i & 0xf; if(b > 9) { b += 7; } SendChar('0'+b); } /* convert a 16 bit value to ASCII and transmi it */ void pr_int(unsigned int i) { SendChar('0' + ((i / 1000) % 10)); SendChar('0' + ((i / 100) % 10)); SendChar('0' + ((i / 10) % 10)); SendChar('0' + (i % 10)); } /* convert a 16 bit unsigned value to ASCII and transmi it */ void pr_uint(unsigned int i) { SendChar('0' + (i / 10000)); SendChar('0' + ((i / 1000) % 10)); SendChar('0' + ((i / 100) % 10)); SendChar('0' + ((i / 10) % 10)); SendChar('0' + (i % 10)); } /* send 4 digit (0-4095) ADC value, followed by a delimiting ":" */ void pr_adc(unsigned int i) { SendChar('0' + ((i / 1000) % 10)); SendChar('0' + ((i / 100) % 10)); SendChar('0' + ((i / 10) % 10)); SendChar('0' + (i % 10)); SendChar(':'); } /* convert an unsigned long to ASCII and transmit it */ void pr_ulong(unsigned long i) { /* pre-calculate some values used multiple times */ /* reduces execution time from 21mSec to 11mSec @ 4MHz */ unsigned int it4 = i / 10000; /* top 4 digits */ unsigned int ib4 = i % 10000; /* bottom 4 digits */ SendChar('0' + ((it4 / 1000) % 10)); SendChar('0' + ((it4 / 100) % 10)); SendChar('0' + ((it4 / 10) % 10)); SendChar('0' + ((it4) % 10)); SendChar('0' + ((ib4 / 1000) % 10)); SendChar('0' + ((ib4 / 100) % 10)); SendChar('0' + ((ib4 / 10) % 10)); SendChar('0' + (ib4 % 10)); } /* routine to send PWM data to servo1 on port RC4 */ /* At 4MHz, minimum pulse width = 474uS, max = 2.51mS */ void pos_servo1(unsigned char i) { /* input is servo position as 0 - 200 */ if(i) { /* zero value == servo turned off */ RC4 = 1; rt_ticks(100); /* 500uSec initial pulse including overheads */ rt_ticks(i); /* until we PASS required count */ rt_ticks(i); /* prevents loop getting optimised out */ RC4 = 0; } } /* outine to send PWM data to servo2 on port RC5 */ void pos_servo2(char i) { if(i) { /* zero value == servo turned off */ RC5 = 1; rt_ticks(100); /* 500uSec initial pulse */ rt_ticks(i); /* 4uSec per tick, want range 0 - 2mSec */ rt_ticks(i); /* so call routine twice */ RC5 = 0; } } /* Check for serial input in the buffer */ /* We want to process completed strings (terminated with a LF (ASCII 10)) */ /* Once the string has been read, reset the buffer pointer for the next one */ void proc_ip(void) { unsigned char n, i; unsigned int j; unsigned char *p; if(RxFlags.EOL) { /* received a full line, do something with it */ RxIPtr = 0; /* start next input string at beginning of buffer */ switch(RxBuf[0] & 0x5F) { /* accept upper or lower case as the same */ case 'S': /* set an output at a given time */ n = RxBuf[1]-'0'; /* bit in port D to set */ j = ((RxBuf[2]-'0')*1000) + ((RxBuf[3]-'0')*100) + ((RxBuf[4]-'0')*10) + (RxBuf[5]-'0'); sched_on[n] = j; break; case 'C': /* clear an output at a given time */ n = RxBuf[1]-'0'; /* bit in port D to set */ j = ((RxBuf[2]-'0')*1000) + ((RxBuf[3]-'0')*100) + ((RxBuf[4]-'0')*10) + (RxBuf[5]-'0'); sched_off[n] = j; break; case 'R': /* Report current state */ timer_bits.READYTOSEND = 1; /* request data */ break; case 'Q': /* 50Hz square wave on / off */ if(RxBuf[1] & 0x1) timer_bits.SQUARE = 1; else timer_bits.SQUARE = 0; break; case 'P': /* PWM for servos, 0 value turns servo off */ n = RxBuf[1]; i = ((RxBuf[2]-'0')*100) + ((RxBuf[3]-'0')*10) + (RxBuf[4]-'0'); if(n == '0') {servo1_val = i; } else { servo2_val = i; } break; case 'W': /* PWM (not servos) count 0 - 1000 */ j = ((RxBuf[1]-'0')*1000) + ((RxBuf[2]-'0')*100) + ((RxBuf[3]-'0')*10) + (RxBuf[4]-'0'); CCPR1L = j >> 2; /* top 8 bits */ CCP1CON = ((j & 0x3) << 4) | 0xC; /* bottom 2 bits of PWM 10-bit value */ /* plus enable PWM mode of hardware */ break; case 'D': /* Set/Clear PORT D bit */ n = RxBuf[1] - '0'; i = (RxBuf[2] & 0x1); if(i) { port_d.ALL = port_d.ALL | (1 << n); } else { port_d.ALL = port_d.ALL & ((1 << n) ^ 0xff); } break; case 'T': /* time delay between data outputs 1 - 99999 ticks */ tx_time = ((RxBuf[1]-'0')*10000) + ((RxBuf[2]-'0')*1000) + ((RxBuf[3]-'0')*100) + ((RxBuf[4]-'0')*10) + (RxBuf[5]-'0'); if((tx_time < 50) && (tx_time != 0)) tx_time = 50; /* limit the max speed to 2 lines / second */ /* 2400 Baud */ if((tx_delay == 0) || (tx_delay > tx_time)) tx_delay = tx_time; /* new value only if a long wait to come */ /* otherwise set new delay after next period */ break; case 'M': /* Memory dump */ SendStr("\nMemory dump\n"); for(p = 0, n = 0; n < 16; n++) { pr_hex((unsigned char)p); SendChar(':'); SendChar(' '); for(i = 0; i < 16; i++) { pr_hex(*p++); SendChar(' '); } SendChar(13); SendChar(10); } SendChar(13); SendChar(10); break; default: SendStr("\nUnrecognised command: 0x"); pr_hex(RxBuf[0]); SendChar('\n'); } RxFlags.ALL = 0; } } /***********************************************************************/ /* Hardware initialisation routines */ /***********************************************************************/ /* TMR0 is an 8 bit counter that is used to count 4s of microseconds */ /* with a prescale value of 8, using Tosc/4 as an input. */ /* At 4MHz, this gives Tosc/4 = 250nS, so divide by 32 = 8uS per count*/ /* for a maximum value of 8 * 255 = 2.040 mSec */ void tmr0_setup() { TMR0 = 0; /* initialise count to zero */ T0IE = 0; /* no iinterrupts */ } /* TMR1 is a 16 bit counter that runs off pin RC0 */ void tmr1_setup() { T1CON = 0x6; /* no prescale, osc off, use T1CKI (RC0), Async */ TMR1H = 0; TMR1L = 0; } /* Initialise TMR2. This is used to provide 10mSec interrupts */ /* which update the internal time (in 1/100 sec tick and second */ /* and also drives the ADC delay loop, which defines the dealy */ /* between sending lines of ADC data to the host */ void tmr2_setup() { tick_10ms.ALL = 0; tick_1s = 0; T2CON = (0x9 << 3) | 0x4 | 0x1; /* 1:10 postscale, timer on, 1:4 prescale */ PR2 = 249; /* 1MHz / (16 * 250 * 5) == 10mSec */ TMR2IE = 1; /* enable TMR2 interrupt */ PEIE = 1; /* turn on PIE interrupts */ } /* Initialise the ADC */ void adc_setup() { ADCON0 = 0x81; /* Fosc/32 (Tad = 2uS) ADC enabled */ ADCON1 = 0x1; /* AN3 = Vref, others as analog inputs */ rt_ticks(4); /* allow acquisition time 11 * Tad */ } /* Initialise the USART */ /* set up for asynchronous mode, 9600 Baud, 8N1, interrupt driven */ void usart_setup() { TXSTA = 0x20; /* Tx enabled, low-speed bitrate gen */ RCSTA = 0x90; /* USART enabled */ SPBRG = 25; /* decimal value for 2400Bd @ 4MHz */ SPBRG = 12; /* value for 4800 Baud @ 4MHz */ RC6 = 1; /* stop bit */ RCIE = 1; /* enable receiver interrupt TXIE = 1; /* enable transmit interrupt */ } /***********************************************************************/ /* Main code starts here */ /* Perform processor initialisation and then call the various routines */ /* to initilalise the peripherals (timers, ADC etc.) and then enable */ /* interrupts */ /* Go into the main program loop, taking ADC measurements and outputting */ /* them as RS232 data. Check for input data that can alter the frequency */ /* of the loop (number of 1/100th second ticks between running it) and */ /* the ID of the chip, which is send as part of the serial data packet. */ /***********************************************************************/ void main(void) { int i; char chn = 0; /* current ADC channel */ unsigned int adc; unsigned char port_c; /* copy of PORT C inputs */ /* The loop delay value is a 32 bit integer. In 1/100ths of a second */ /* allows for delays between readings of (2**32)/100 ~ 43million */ unsigned char t10ms; unsigned long t1s; asm("movf _STATUS, w"); asm("movwf _power_up"); power_up &= 0x18; /* isolate /TO and /PD bits */ power_up |= (PCON & 0x3); INTCON = 0; /* no interrupts - ever! */ OPTION = 0x1; /* Weak pullup enabled, TMR0 */ /* clock = int, TMR0 prescale = 4 4uS ticks) */ TRISA = 0x3f; /* Analog pins input, A4 T0CKi input */ TRISB = 0xff; /* PORT B all inputs */ TRISC = 0x81; /* RC7 = RxD (in) */ /* RC3,4 = Servo (out) */ /* RC2 = PWM (out), RC3 = buzzer (out) */ /* RC1 = LED flasher (out) */ /* RC0 = T1CKI (in) */ TRISD = 0; /* All outputs */ TRISE = 0x7; /* Analog ports are inputs */ tmr0_setup(); tmr1_setup(); tmr2_setup(); adc_setup(); usart_setup(); port_d.ALL = 0; /* start with all outputs at zreo */ tick_10ms.ALL = 0; timer1.ALL = 0; timer1_prev = 0; timer1_1sec = 0; rb7_changes = 0; rb6_changes = 0; interval_tmr = 0; interval_val = 0; servo1_val = 0; /* initialise with servos turned off */ servo2_val = 0; tx_time = 0; /* by default, only send data when asked */ tx_delay = 0; TxFlags.ALL = 0; RxFlags.ALL = 0; RxIPtr = 0; RxOPtr = 0; TxIPtr = 0; TxOPtr = 0; GIE = 1; /* allow interrupts when all set up */ RC4 = 0; /* servo outputs to known state */ RC5 = 0; RC7 = 1; /* idle state for serial port is 1. */ /* Announce the start of the program */ rt_ticks(250); SendStr("\n\n"); if((power_up & 0x13) == 0x03) SendStr("Power-up OK "); if((power_up & 0x18) == 0x10) SendStr("Brown-out reset "); if((power_up & 0x1b) == 0x19) SendStr("WDT reset "); if((power_up & 0x18) == 0x18) SendStr("MCLR reset "); SendStr("GPIO v2.0 (4MHz) 21-Jul-2009\n"); /***********************************************************************/ for (;;) { if(timer_bits.READYTOSEND) { chn = 0; /* have to reset the ADC channel, or we don't */ /* get the headers and trailer in the right place */ timer_bits.SENDNOW = 1; /* hit zero, ready to send */ timer_bits.READYTOSEND = 0; } /***********************************************************************/ PORTD = port_d.ALL; /* update outputs each time round the loop */ chn = chn & 0x7; /* restrict range to 0 - 7 */ if(chn == 0 && timer_bits.SENDNOW) { pr_hex(port_b.ALL); SendChar(':'); pr_int(rb7_changes); rb7_changes = 0; SendChar(':'); pr_int(rb6_changes); rb6_changes = 0; SendChar(':'); pr_hex(port_d.ALL); SendChar(':'); } adc = read_adc(chn); if(timer_bits.SENDNOW) pr_adc(adc); /* output the 4-digit value, followed by a ':' */ chn++; /* now see if the time has come to actuate the servos */ /* check the 20mSec flag from the ISR and tickle the servos */ /* according to the values from the ADC */ if(timer_bits.SERVO) { timer_bits.SERVO = 0; /* reset for next time */ pos_servo1(servo1_val); pos_servo2(servo2_val); } /***********************************************************************/ /* This is the postamble, send all the stuff after the last ADC reading */ if(chn == 8 && timer_bits.SENDNOW) { /* done all the ADC channels */ pr_ulong(timer1_1sec); /* 1 second frequency measurement */ SendChar(':'); pr_uint(interval_val); SendChar(':'); interval_val = 0; /* only send the data once */ /* re-run servo code as the pr_ulong() routine takes over 20mS to execute */ /* so we may have lost a tick while it runs */ if(timer_bits.SERVO) { timer_bits.SERVO = 0; /* reset for next time */ pos_servo1(servo1_val); pos_servo2(servo2_val); } TMR2IE = 0; /* prevent timer updates while we''re reading it */ t1s = tick_1s; t10ms = tick_10ms.ALL; TMR2IE = 1; pr_ulong(t1s); /* send the seconds count */ SendChar('.'); pr_ch99(t10ms); /* hundreths */ /*** Note. Trying to put the servo code in here, too crashes the simulator */ SendChar(13); /* CR LF pair to terminate the output */ SendChar(10); /* reset the send_data flag, ready for the next loop */ timer_bits.SENDNOW = 0; } /***********************************************************************/ proc_ip(); /* could possibly request data to be sent */ } /******** end of main loop *********/ } /******** end of main code *********/