/********************************************************************* * Description of program: * * C file for testing self build PIC16F877 board with ICD * * Just testing functionality of connection between PC->ICD->PIC * * by donwloading ICD soft to PIC and using on board hardware. * * That are: * * - the two buttons and leds * * connected to RD5 and RD6 as well as the two LEDs attached to * * RB0 and RB1. * * - on/off switch and led * * activated only by pos. edge * * - two pots connected to analog input * * connected to AN0 and AN1 * * - two PWM channels * * CAPCOM modules 1 and 2 * * - serial port ( not implemented yet ) * * * * * * * * * * * * * * * * So much for the purpose of this file * ********************************************************************* * Author: * * Max. Heise, 15.03.2002, 16H10 - created * * Max. Heise, 18.03.2002, 12H00 - Changed so that the two pairs of * * LEDs function the opposite way, one is on, the other off by * * default. This is better because the LED on will signal that the * * program was loaded correctly and the PIC is running. * * Max. Heise, 19.03.2002, 14H40 - Copied asm functions from project * * pwm_f877 and converted them to pseudo C(Inline assembler). * * Max. Heise, 19.03.2002, 19H00 - Conversion to pure C complete * * Max. Heise, 20.03.2002, 10H30 - error in generating mask for LSB * * PWM register, added enable and IN2s for PWM, rewrote * * startSwitchIsOn to faster handle the default case that the * * switch is on. Added init for ENs, IN2s and switch to port init * * functions. More comments. * * * ********************************************************************* * TODO/Bugs: * * * *********************************************************************/ /******************* * Defines *******************/ #define TRUE 1 #define FALSE 0 #define PWM0ENABLE RD3 #define PWM1ENABLE RC5 #define SWITCH0 RD4 #define BUTTON0 RD6 #define BUTTON1 RD5 #define LED0 RB1 #define LED1 RB0 #define LED2 RD7 /******************* * Includes *******************/ #include #include /******************* * Macros *******************/ #define bsf(file,index) ((file)|=1<<(index)) #define bcf(file,index) ((file)&=~(1<<(index))) /******************* * global variables *******************/ char counter; /* for function waitRAT /******************************************************************************************** * Init of PWM channels * Init for PWM mode of Capcom module, p. 61 * PR2's value is calculated like that: * We need a frequency of at least 15kHz, to * avoid hearing the motors. That gives us a * period Tpwm of 6,67,e-5, the formula is on * p. 61 of the PIC16F877 documentation. * With TMR2 prescaler set to 1 it is necessary * to set PR2 to decimal 65=0x41. * Duty cycle, 10 bits: * 00 0000 0000b = 0d * 00 1111 1111b = 255d = 1/4 = default * 01 1111 1111b = 511d = 1/2 * 10 1111 1111b = 767d = 3/4 * 11 1111 1111b = 1023d * 8 MSB Bits in CCPR1L or CCPR2L: * 0011 1111 = 0x3F * 1011 1111 = 0xBF * PWM Mode for Capcom module is initialised * by setting CCP1CON and CCP2CON file bits * 3:0 to binary 11XX. ********************************************************************************************/ void initPWM(void) { /************** * Setup PR2 **************/ PR2=0x41; /**************************************** * Set duty cycle 8 MSB Bits to 0, do not * forget the 2 LSB later ! ****************************************/ CCPR1L=0x00; CCPR2L=0x00; /************************************************** * make RC2/CCP1 and RC1/CCP2 pins an output, p. 29 * by clearing the pins **************************************************/ PORTC=0x00; TRISC&=0xF9; /* 1111 1001 */ /****************************************************************** * set Timer 2 TMR2 prescale value to 1 by clearing bits 1:0, p. 55 * and start the timer by setting bit 2 * 0000 0100=0x04 ******************************************************************/ T2CON=0x04; /*********************************************************** * setup CCP1,CCP2 for PWM operation * clear 2 LSB bits 5:4, set bits 3:0 * to 1100 for PWM mode * 0000 1100=0x0C * 0x00 to clear the two LSB bits of the PWM duty cycle * 0x30 to set the two LSB bits of the PWM duty cycle * 0x0C to set PWM operation of CCP1, CCP2 module ***********************************************************/ CCP1CON=0x0C; CCP2CON=0x0C; /************************************************* * Both CCP1 and CCP2 should now be configured for * PWM operation, but the duty cycle is still set * to 0, so no signal should appear. *************************************************/ } /******************************************************************************************** * Init of A/D converter module; p.111 * We are using RA0/AN0 and RA1/AN1 as * analog inputs beware that AN7, AN6 and * AN5 are *not* available on 28pin devices * such as 873, 874 * Unfortunately there is no mode for ADCON1, * p. 112 with CHAN/REFS 2/0, so we have to * sacrify one pin, RA3/AN3, and leave it as an * unused analog input. Mode 3/0 is choosen * by setting bits 3:0 of ADCON1 to 0x04, * binary 0100. ********************************************************************************************/ void initAD(void) { /********************************** * set TRISA 0,1,3 bits for analog * inputs **********************************/ TRISA|=0x0B; /* 0000 1011 */ /******************************************** * ADCON1, format left justified, clear bit 7 * mode 3/0 bits 3:0 binary 0100 ********************************************/ ADCON1=0x04; /****************************************************************** * at the beginning select input channel AN0, bits 5:3 * set to 000, later toggle between channel 0 and channel 1. * Setting for channel 1 is 001 * We are using a 4MHz clock, so regarding table 11-1, p. 116 * we should use a operation time of 8Tosc for a conversion * that is bits 7:6 of ADCON0 set to binary 01 * thus we get a binary value of 01 000 0 0 1 or 0100 0001 or 0x41 * for channel 0. For channel 1 the ADCON0 value is * 01 001 0 0 1 equal 0x49 ******************************************************************/ ADCON0=0x41; /********************************************* * init is now complete, to use the A/D module * follow with step 3., p. 113 *********************************************/ } /******************************************************************************************** * Init of PortA is not needed yet, but analog channels on port A are set up in initAD. ********************************************************************************************/ /******************************************************************************************** * Init of PortB ********************************************************************************************/ void initPortB(void) { /******************* * Outputs: clear TRISB bits * RB1 LED * RB0 LED *******************/ TRISB&=0xFC; // 0xFC -> 1111 1100 /******************* * clear port * register PORTB *******************/ PORTB=0; } /******************************************************************************************** * Init of PortC ********************************************************************************************/ void initPortC(void) { /********************************** * Note that RC1/CCP2 and RC2/CCP1 * are outputs, too. But these are * set in function initPWMChannel() ********************************** /******************* * Outputs: clear TRISC bits * RC5 EN1 * RC4 IN12 * (RC1 CCP2,PWM1) * (RC2 CCP1,PWM0) *******************/ TRISC&=0xCF; /* 1100 1111 */ /******************* * clear port * register PORTC *******************/ PORTC=0; } /******************************************************************************************** * Init of PortD ********************************************************************************************/ void initPortD(void) { /******************* * Inputs: set TRISD bits * RD6 button * RD5 button * RD4 switch *******************/ TRISD|=0x70; /* 0111 0000 */ /******************* * Outputs: clear TRISD bits * RD7 LED for switch * RD3 EN0 * RD2 IN02 *******************/ TRISD&=0x73; /* 0111 0011 */ /********************** * clear port register * PORTC **********************/ PORTC=0; } /******************************************************************************************** * Toggle the A/D channel used for sampling * an analog value from the the potentiometers * see comment in function above * For channel 0 the ADCON0 value is 0x41 * For channel 1 the ADCON0 value is 0x49 * ADCON1 does not change * Location of files: * ADCON0 bank 0, 0x1F ********************************************************************************************/ void toggleADChannel(void) { if(ADCON0==0x41) { /***************************** * Channel 0 currently selected * now switch to channel 1 *****************************/ ADCON0=0x49; } else { /***************************** * Channel 1 currently selected * now switch to channel 0 *****************************/ ADCON0=0x41; } } /******************************************************************************************** * Toggles the PWM Channel. * If FSR points to CCPR1L/0x15 we must point it * to CCPR2L/0x1B and vice versa. * Note that this function, as well as toggleADChannel, could also be writen in ?: syntax, * but this, with the inline comments, is more verbose and easier to understand for maintainers * of this code later on. * The two registers are always acting in pairs, so no need to check both in the if statement * below. ********************************************************************************************/ void togglePWMChannel(volatile char **pDutyCycleMSB, volatile char **pDutyCycleLSB) { if((*pDutyCycleMSB)==&CCPR1L) { /****************************** * PWM Channel 0 currently sel. * Switch to PWM Channel 1 ******************************/ *pDutyCycleMSB=&CCPR2L; *pDutyCycleLSB=&CCP2CON; } else { /****************************** * PWM Channel 1 currently sel. * Switch to PWM Channel 0 ******************************/ *pDutyCycleMSB=&CCPR1L; *pDutyCycleLSB=&CCP1CON; } } /******************************************************************************************** * Required acquisition time for A/D module * p. 115 equation 11-1 Tacq=19,72,e-6 * seconds, approx. 2,e-5 seconds. * One instruction is executed in one * cycle, one cycle at 4MHz takes 2,5,e-7 * seconds. Thus we must idle for 80 * instructions * Formula for this loop * Inst(I)=1+1+( (I-1)*(1+2) )+2+2=6+3I-3=3I+3 * +2 Inst.Cycles for the call of this function * => Inst(I)=3I+5 * Inst(I) must be 80 * 80=3I+5 * => I=25 * Timing critical, so this is still in assembler. ********************************************************************************************/ void waitRAT(void) { #asm I EQU 25 movlw I ; 1 Inst.Cycle movwf _counter ; 1 Inst.Cycle WaitRAT_Loop decfsz _counter ; 1 Inst.Cycle if I!=0, 2 Inst.Cycles if I==0 goto WaitRAT_Loop ; 2 Inst.Cycles #endasm } /******************************************************************************************** * Wait for A/D conversion to complete * Wait for result, A/D conversion is complete * when bit ADCON0,2 is cleared by hardware. * We only have to wait .... ********************************************************************************************/ void waitADComplete(void) { while((ADCON0&0x04)!=0x00) { /******************* * A/D conversion still * running, do nothing *******************/ ; /* NOP */ } } /******************************************************************************************** * Start A/D conversion by setting bit 2 in ADCON0 * One should wait at least 2 Tad, * p. 115 Note 4 before starting the next A/D * conversion. 2 Tad is, table 11-1, * p. 116 16Tosc is equal 4,e-6 seconds which * is equal 16 Inst.Cycles. * So lets hope that we have enough other things to do * to spend these 16 inst. cycles elsewhere before making the * next A/D conversion ;) ********************************************************************************************/ startADConversion(void) { ADCON0|=0x04; /* 0000 0100 */ } /******************************************************************************************** * PWMChanEN * Sets or Resets the enable for PWM channel 0 or 1 ********************************************************************************************/ void PWMChanEN(char channel, char state) { /************************* * check parameters, this is * why I like objects ..... *************************/ if(channel > 1 || channel < 0 || state > 1 || state < 0 ) { /* scream error, but how ? Perhaps a asm goto to address 0x00 ? */ } if(channel==0) { /********** * chan0 **********/ PWM0ENABLE=state; } else { /********** * chan1 **********/ PWM1ENABLE=state; } } /******************************************************************************************** * startSwitchIsOn * It is not sufficent that RD4 is HIGH, we need to observe a transition from LOW to HIGH * (positive edge), therefor a bit more logic is needed than for the other two buttons. * There are 3 possibilities: * 1. RD4 is LOW, return 0 * 2. RD4 is HIGH, no pos. edge was ever observed, return 0 * 3. RD4 is HIGH and a pos. edge has once been observed, return 1 ********************************************************************************************/ char startSwitchIsOn(void) { /************************** * Variables **************************/ static bit lastState; static bit currentState; static bit posEdgeObserved; static bit wasHereBefore; char retval=0; /* bits can either be static or global, but never local variables * with this C compiler */ /************************* * store current state * and get new state *************************/ lastState=currentState; currentState=SWITCH0; /************************* * This to avoid having the * whole thing start on power * up when SWITCH0 is in HIGH * position. *************************/ if( !wasHereBefore ) { wasHereBefore=1; lastState=currentState; } /************************** * Check for pos. edge **************************/ if( (!lastState) && currentState ) { /********************* * This is a pos edge! *********************/ posEdgeObserved=1; } /********************************* * Now check the three possibil. * listed above *********************************/ if( (!currentState) || (currentState && (!posEdgeObserved))) { posEdgeObserved=0; retval=0; } else { if( currentState && posEdgeObserved ) { retval=1; } } /************************* * if we return TRUE then * switch on the led RD7 and * enable the two PWM channels * else switch off * and finally return from function .... *************************/ if(retval) { LED2=1; PWMChanEN(0, 1); PWMChanEN(1, 1); } else { LED2=0; PWMChanEN(0, 0); PWMChanEN(1, 0); } return retval; } /******************************************************************************************** * led0 ********************************************************************************************/ void buttonled0(void) { /****************** * First pair of * button+LED ******************/ if(!BUTTON0) { /****************** * RD5 low ******************/ LED0=1; } else { /****************** * RD5 high ******************/ LED0=0; } } /******************************************************************************************** * led1 * Note that buttonled0 and buttonled1 are functioning with inverted meaning. ********************************************************************************************/ void buttonled1(void) { /****************** * Second pair of * button+LED ******************/ if(BUTTON1) { /****************** * RD6 high ******************/ LED1=1; } else { /****************** * RD6 low ******************/ LED1=0; } } /******************************************************************************************** * Main ********************************************************************************************/ void main(void) { /******************* * local variabes *******************/ volatile char *pDutyCycleMSB=&CCPR1L; volatile char *pDutyCycleLSB=&CCP1CON; /******************* * Init ports *******************/ initPortB(); initPortC(); initPortD(); initPWM(); initAD(); /******************* * Endless loop *******************/ while(TRUE) { while(startSwitchIsOn()) { /*********************** * Do the A/D Conversion ***********************/ waitRAT(); startADConversion(); waitADComplete(); /*********************** * Result of A/D conversion, * is found in ADRESH:ADRESL * left justified, 6 LSB bits * of ADRESL are read as 0. * PWM 2 LSB bits are stored * in CCPxCON<5:4> * if 2 LSB bits in ADRESL are * set: * ADRESL: 1100 0000 * CCPxCON: nn11 nnnn n->don't change ***********************/ *pDutyCycleMSB=ADRESH; /*************************** * First clear the bits 5:4 * in CCPxCON. Then set to * result in ADRESL ***************************/ (*pDutyCycleLSB)&=0xCF; /* 1100 1111 */ (*pDutyCycleLSB)|=(ADRESL>>2); /******************** * toggle the channels ********************/ toggleADChannel(); togglePWMChannel(&pDutyCycleMSB,&pDutyCycleLSB); /******************** * normal buttons+leds ********************/ buttonled0(); buttonled1(); } /* End of while(startSwitchIsOn()) */ } /* End of while(true) */ } /* End of main */ /******************************************************************************************** * EOF ********************************************************************************************/