/******************************************************************************** * MAHE, 13.05.2002, 12H40 - now compiling as project of separated source files * MAHE, 14.05.2002, 16H00 - spend all day hunting a bug which was that the return value * of getADValue returned the high byte and the low byte inversed. ********************************************************************************/ #include "ad.h" extern unsigned char errorno; /****************************** * for function waitRAT * May no be static, or the compiler * will complain ******************************/ char counter; /******************************************************************************************** * Init of A/D converter module; p.111 * Using RA0/AN0 and RA1/AN1 as analog inputs for the potis. * Using RA2/AN2, RA3/AN3 and RA5/AN4 for light reflexion sensors. * Beware that AN7, AN6 and AN5 are *not* available on 28pin devices * such as the 16F873, 16F874 * Mode 5/0, p.112 is choosen * by setting bits PCFG3:PCFG0 3:0 of ADCON1 to binary 0010, hex 0x02. ********************************************************************************************/ void initAD(void) { /********************************** * set TRISA 0,1,2,3, 5 bits for analog * inputs **********************************/ TRISA|=0x2F; /* 0010 1111 */ /******************************************** * ADCON1, format right justified, set bit 7 * mode 5/0 bits 3:0 binary 0010, p.112 ********************************************/ ADCON1=0x82; /* 1000 0010 */ /****************************************************************** * Channel ADCON0<5:3> * 0 000 * 1 001 * 2 010 * 3 011 * 4 100 * at the beginning select input channel AN0, bits 5:3 * set to 000, later toggle between channels * 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. ******************************************************************/ ADCON0=0x41; /********************************************* * init is now complete, to use the A/D module * follow with step 3., p. 113 *********************************************/ } /******************************************************************************** * Setup ADCON0 for channel chan * bin, dec * For channel 0 the ADCON0<5:3> is 000, 0 * For channel 1 the ADCON0<5:3> is 001, 1 * For channel 2 the ADCON0<5:3> is 010, 2 * For channel 3 the ADCON0<5:3> is 011, 3 * For channel 4 the ADCON0<5:3> is 100, 4 * ADCON1 does not change * Location of files: * ADCON0 bank 0, 0x1F ********************************************************************************/ void setADChannel(const unsigned char chan) { /****************************** * stores number of last ad channel * selected ******************************/ static unsigned char lastADChannel; /*************************** * Blank bits 5:3 ***************************/ char writevalue=ADCON0 & 0xC7; /* 1100 0111 */ /****************************** * Create the mask for selecting the * channel from function parameter * by shifting chan to bits 5:3 * Example * bits: 7654 3210 * chan: 0000 0011 channel 3 * chan << 3: 0001 1000 ******************************/ char mask=(chan<<3); /****************************** * Store last selected chan no ******************************/ lastADChannel=chan; switch(chan) { case 0: case 1: case 2: case 3: case 4: writevalue|=mask; break; default: /****************************** * This has the site effect that * channel is set to 0 because * writevalue is not changed any * more ******************************/ errorno=ERR_ADCHANNELNO; break; } ADCON0=writevalue; } /******************************************************************************************** * Toggle the A/D channel used for sampling an analog value * from the the potentiometers or the light sensors ********************************************************************************************/ void toggleADChannel(void) { char readvalue; /*************************** * Blank out all bits except * ADCON0<5:3> and shift it 3x * right, this is easier for the * switch/case ***************************/ readvalue=ADCON0 & 0x38; /* 0011 1000 */ readvalue=readvalue>>3; /***************** * Now toggle the channel *****************/ switch(readvalue) { case 0: /***************************** * Channel 0 currently selected * now switch to channel 1 *****************************/ setADChannel(1); break; case 1: /***************************** * Channel 1 currently selected * now switch to channel 2 *****************************/ setADChannel(2); break; case 2: /***************************** * Channel 2 currently selected * now switch to channel 3 *****************************/ setADChannel(3); break; case 3: /***************************** * Channel 3 currently selected * now switch to channel 4 *****************************/ setADChannel(4); break; case 4: /***************************** * Channel 4 currently selected * now switch to channel 0 *****************************/ setADChannel(0); break; } } /******************************************************************************************** * 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 50 ; Note this is actually twice the required value of I, just to be sure movlw I ; 1 Inst.Cycle movwf _counter ; 1 Inst.Cycle WaitRAT_Loop decfsz _counter,f ; 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 ;) ********************************************************************************************/ void startADConversion(void) { ADCON0|=0x04; /* 0000 0100 */ } /******************************************************************************** * Returns the AD conversion's value as unsigned int * Endianness is just a big f**king mess on a µP * The PICC uses little endian format to emulate types of more than 8 bits * retval=ADRESH will assign ADRESH to the low byte of retval, then we have to shift * it to the high byte. ********************************************************************************/ unsigned int getADValue(void) { unsigned int retval=0; retval=ADRESH; retval=retval<<8; retval+=ADRESL; if(retval > ADMAXVALUE) { retval=0; errorno=ERR_IMPOSSIBLE_AD_VALUE; } return retval; }