View previous topic :: View next topic |
Author |
Message |
future
Joined: 14 May 2004 Posts: 330
|
Fast ADC code |
Posted: Sun Nov 28, 2004 9:13 pm |
|
|
Hi all,
I have some working code and am trying to improve it.
Every 1ms I set the adon bit and wait to get the interrupt, but the dispatcher overhead is big for a simple int.
So I am looking for a simpler way to get the AD data. Something like this but in the main code... a function that every time I call it it returns the last adc read.
Code: | bit_set(adcon0,adon); // read next a/d channel
#int_ad
void ad_isr(void) {
adcraw[adch]=adres; // return value in a/d result reg
bit_clear(++adch,3); // rollover after 7
set_adc_channel(adch); // write channel to adcon0
} |
Any snippets would be good.
Thank you. |
|
|
Charlie U
Joined: 09 Sep 2003 Posts: 183 Location: Somewhere under water in the Great Lakes
|
|
Posted: Mon Nov 29, 2004 7:48 am |
|
|
This depends on how recent your version of the compiler is, but you could try something like this:
Code: |
void my_adc_read(void) {
adcraw[adch]=read_adc(ADC_READ_ONLY);
bit_clear(++adch,3); // rollover after 7
set_adc_channel(adch);
// add delay here for data acquisition time
// the 18F452 data sheet has an example
// that shows approximately 13 microseconds
delay_us(13);
read_adc(ADC_START_ONLY);// write channel to adcon0
}
|
You must make certain that there is enough time between calls to this function to allow for the conversion time.
Also, somewhere before calling this function, you must start a conversion on the first channel to get things started. |
|
|
future
Joined: 14 May 2004 Posts: 330
|
|
Posted: Mon Nov 29, 2004 1:51 pm |
|
|
This is where lives my problem, I cant stop processing data and wait 13uS, I have a 20uS timer interrupt.
I will have to look on the oscilloscope how the waveforms will be after this change.
The program controls 16 pulse outputs with a 20uS resolution. |
|
|
Neutone
Joined: 08 Sep 2003 Posts: 839 Location: Houston
|
|
Posted: Mon Nov 29, 2004 3:49 pm |
|
|
I would guess you have plenty of code space. You can call this as often as you wish without using interupts and the channels will have enougn time to settle before reading. It also filters the inputs over time creating a 12 bit reading from a 10 bit input.
Code: | #define ADCON0 0xFC2
#bit GO_DONE = ADCON0.1
Int16 Analog_0_RAW;
Int16 Analog_0_Work;
Int16 Analog_0_Filtered;
Int16 Analog_1_RAW;
Int16 Analog_1_Work;
Int16 Analog_1_Filtered;
Int16 Analog_2_RAW;
Int16 Analog_2_Work;
Int16 Analog_2_Filtered;
Int16 Analog_3_RAW;
Int16 Analog_3_Work;
Int16 Analog_3_Filtered;
Int16 Analog_4_RAW;
Int16 Analog_4_Work;
Int16 Analog_4_Filtered;
Int16 Analog_5_RAW;
Int16 Analog_5_Work;
Int16 Analog_5_Filtered;
Int16 Analog_6_RAW;
Int16 Analog_6_Work;
Int16 Analog_6_Filtered;
Int16 Analog_7_RAW;
Int16 Analog_7_Work;
Int16 Analog_7_Filtered;
Int8 Analog_Channel;
#bit Analog_0 = Analog_Channel.0
#bit Analog_1 = Analog_Channel.1
#bit Analog_2 = Analog_Channel.2
#bit Analog_3 = Analog_Channel.3
#bit Analog_4 = Analog_Channel.4
#bit Analog_5 = Analog_Channel.5
#bit Analog_6 = Analog_Channel.6
#bit Analog_7 = Analog_Channel.7
Int1 Analog_Been_Initialized=0;
/***********************************************************
* Analog *
***********************************************************/
#inline
void Analog_Service(void)
{ if(!Analog_Been_Initialized)
{ setup_port_a( RA0_ANALOG );
setup_adc_ports( ALL_ANALOG );
setup_adc(ADC_CLOCK_INTERNAL);
Analog_Been_Initialized=1;
Analog_Channel=0;
}
else
{ if((Analog_Channel<<1)==0) Analog_Channel++; // Advance to the next channel
if(Analog_0)
{ set_adc_channel(0);
GO_DONE=1;
Analog_0_Work+=Analog_0_RAW;
Analog_0_Filtered=(Analog_0_Work>>2);
Analog_0_Work-=Analog_0_Filtered;
While(GO_DONE);
Analog_0_RAW=read_adc();
}
if(Analog_1)
{ set_adc_channel(1);
GO_DONE=1;
Analog_1_Work+=Analog_1_RAW;
Analog_1_Filtered=(Analog_1_Work>>2);
Analog_1_Work-=Analog_1_Filtered;
While(GO_DONE);
Analog_1_RAW=read_adc();
}
if(Analog_2)
{ set_adc_channel(2);
GO_DONE=1;
Analog_2_Work+=Analog_2_RAW;
Analog_2_Filtered=(Analog_2_Work>>2);
Analog_2_Work-=Analog_2_Filtered;
While(GO_DONE);
Analog_2_RAW=read_adc();
}
if(Analog_3)
{ set_adc_channel(3);
GO_DONE=1;
Analog_3_Work+=Analog_3_RAW;
Analog_3_Filtered=(Analog_3_Work>>2);
Analog_3_Work-=Analog_3_Filtered;
While(GO_DONE);
Analog_3_RAW=read_adc();
}
if(Analog_4)
{ set_adc_channel(4);
GO_DONE=1;
Analog_4_Work+=Analog_4_RAW;
Analog_4_Filtered=(Analog_4_Work>>2);
Analog_4_Work-=Analog_4_Filtered;
While(GO_DONE);
Analog_4_RAW=read_adc();
}
if(Analog_5)
{ set_adc_channel(5);
GO_DONE=1;
Analog_5_Work+=Analog_5_RAW;
Analog_5_Filtered=(Analog_5_Work>>2);
Analog_5_Work-=Analog_5_Filtered;
While(GO_DONE);
Analog_5_RAW=read_adc();
}
if(Analog_6)
{ set_adc_channel(6);
GO_DONE=1;
Analog_6_Work+=Analog_6_RAW;
Analog_6_Filtered=(Analog_6_Work>>2);
Analog_6_Work-=Analog_6_Filtered;
While(GO_DONE);
Analog_6_RAW=read_adc();
}
if(Analog_7)
{ set_adc_channel(7);
GO_DONE=1;
Analog_7_Work+=Analog_7_RAW;
Analog_7_Filtered=(Analog_7_Work>>2);
Analog_7_Work-=Analog_7_Filtered;
While(GO_DONE);
Analog_7_RAW=read_adc();
}
}
}
|
|
|
|
future
Joined: 14 May 2004 Posts: 330
|
|
Posted: Mon Nov 29, 2004 6:51 pm |
|
|
Neutone:
Thanks for the reply.
Code: | if((Analog_Channel<<1)==0) Analog_Channel++; // Advance to the next channel |
I don't get how it walks through the channels, I think this would work only for the 0 going to 1 channel, but how it will increment to 2?
I will use 8 bit a/d without any filtering.
Another thing, why use GO_DONE=1 if read_adc() will do it all again? |
|
|
Ttelmah Guest
|
|
Posted: Tue Nov 30, 2004 3:49 am |
|
|
future wrote: | This is where lives my problem, I cant stop processing data and wait 13uS, I have a 20uS timer interrupt.
I will have to look on the oscilloscope how the waveforms will be after this change.
The program controls 16 pulse outputs with a 20uS resolution. |
In all honesty, if you have a 20uSec interrupt, use it for the ADC as well.
The big problem though is that even at 40MHz, a 20uSec interrupt will not lave much time for anything else. The overhead on the normal interrupt handler, is about 60-80 instruction times (once all the registers are saved, the interrupt checked, the subroutine called, and the registers restored). Even at 10MIPS, nearly half the processor time is going to be used in the interrupt.
That being said, run a single byte state machine, on the first interrupt, select the channel. On the second, trigger the conversion. On the third, read it. If the data is written into an array, it can be accessed asynchronously as needed.
If you use a switch statement, with no 'default', though it looks bulky, it'll translate into a table based jump, which is quite efficient. Avoid using variables to access the array. Making the switch longer in the table based form, adds no extra time, so something like:
Code: |
switch (state++) {
case 0:
set_adc_channel(0);
break;
case 1:
read_adc(ADC_START_ONLY);
break;
case 2:
adval[0]=make16(ADRESH,ADRESL);
break;
case 3:
set_adc_channel(1);
break;
case 4:
read_adc(ADC_START_ONLY);
break;
case 5:
adval[1]=make16(ADRESH,ADRESL);
break;
..... repeat for all eight cases.
case 23:
adval[7]=make16(ADRESH,ADRESL);
state=0;
break;
}
|
Now though this is bulky, it is actually quick. The compiler will generate a table based jump for this code, and each entry, only involves a few of instructions. Using 'make16', rather that 'ADC_READ_ONLY', implies the compiler will not bother to check the 'conversion done' flag, and simply transfer the two bytes to fixed addresses (an access to an array using a variable, involves calculating the address to use at run time, and is slow - doing the same using a constant, is solved at compile time, and is therefore fast). Since the interrupts are at 20uSec, enough time exists for each of the phases involved (charge and read), without having to delay.
It may be that you are handing the global interrupt event yourself, and thereby reducing the interrupt overhead, by saving less registers. If so, you'l have to add the table access registers used for the switch statement to this.
Best Wishes |
|
|
Neutone
Joined: 08 Sep 2003 Posts: 839 Location: Houston
|
|
Posted: Tue Nov 30, 2004 9:39 am |
|
|
future wrote: | Neutone:
Thanks for the reply.
Code: | if((Analog_Channel<<1)==0) Analog_Channel++; // Advance to the next channel |
I don't get how it walks through the channels, I think this would work only for the 0 going to 1 channel, but how it will increment to 2?
I will use 8 bit a/d without any filtering.
Another thing, why use GO_DONE=1 if read_adc() will do it all again? |
This
Shifts a bit left through a byte. If the resulting byte is zero the byte is incremented. The byte has 8 different values.
This
will be cleared 12 TAD later allowing time for the input to settle. |
|
|
future
Joined: 14 May 2004 Posts: 330
|
|
Posted: Tue Nov 30, 2004 2:33 pm |
|
|
Ttelmah, that's what I was talking about! The code works perfect, thank you.
This 20uS interrupt is really hard for the cpu I know, but the highest resolution I can get better are the results.
There are 14 outputs controlled by this interrupt.
I tried to use FAST INTS but I lost the fight... got weird behaviour from the program. It works most of the time but sometimes it computes weird data.
Neutone, does if((Analog_Channel<<1)==0) changes the value of Analog_Channel? |
|
|
Ttelmah Guest
|
|
Posted: Tue Nov 30, 2004 4:37 pm |
|
|
Glad it worked.
To use the fast interrupt, you really need to go through the assembler code generated for the routines you call, and verify exactly which registers are used. Remember the fast stack, only saves the very 'main' registers, and you manually need to save the 'scratch' area if you do any arithmetic, or subroutine calls that are not 'inline'. You also need to save the indirect addressing registers if you access an array etc..
I have used it on half a dozen occasions now, and in and example like yours, you can probably save about 25% of the overhead (especially if there is only one fast interrupt, since then you don't have to check any interrupt flags).
There are problems with the way it is implemented by CCS at present, which makes it difficult to be efficient in this area (I usually end up with some assembler added).
Best Wishes |
|
|
Mark
Joined: 07 Sep 2003 Posts: 2838 Location: Atlanta, GA
|
|
Posted: Tue Nov 30, 2004 6:34 pm |
|
|
future wrote: | Ttelmah, that's what I was talking about! The code works perfect, thank you.
This 20uS interrupt is really hard for the cpu I know, but the highest resolution I can get better are the results.
There are 14 outputs controlled by this interrupt.
I tried to use FAST INTS but I lost the fight... got weird behaviour from the program. It works most of the time but sometimes it computes weird data.
Neutone, does if((Analog_Channel<<1)==0) changes the value of Analog_Channel? |
No only this line
changes Analog_Channel |
|
|
future
Joined: 14 May 2004 Posts: 330
|
|
Posted: Tue Nov 30, 2004 6:55 pm |
|
|
So it will only increment the channel if it was 0b10000000 as there is no other Analog_Channel++. |
|
|
Mark
Joined: 07 Sep 2003 Posts: 2838 Location: Atlanta, GA
|
|
Posted: Tue Nov 30, 2004 8:13 pm |
|
|
Or if it is 0b00000000. I think he made a mistake. It should be (Analog_Channel<<=1)
These elses should make it faster as well
Code: |
#define ADCON0 0xFC2
#bit GO_DONE = ADCON0.1
Int16 Analog_0_RAW;
Int16 Analog_0_Work;
Int16 Analog_0_Filtered;
Int16 Analog_1_RAW;
Int16 Analog_1_Work;
Int16 Analog_1_Filtered;
Int16 Analog_2_RAW;
Int16 Analog_2_Work;
Int16 Analog_2_Filtered;
Int16 Analog_3_RAW;
Int16 Analog_3_Work;
Int16 Analog_3_Filtered;
Int16 Analog_4_RAW;
Int16 Analog_4_Work;
Int16 Analog_4_Filtered;
Int16 Analog_5_RAW;
Int16 Analog_5_Work;
Int16 Analog_5_Filtered;
Int16 Analog_6_RAW;
Int16 Analog_6_Work;
Int16 Analog_6_Filtered;
Int16 Analog_7_RAW;
Int16 Analog_7_Work;
Int16 Analog_7_Filtered;
Int8 Analog_Channel;
#bit Analog_0 = Analog_Channel.0
#bit Analog_1 = Analog_Channel.1
#bit Analog_2 = Analog_Channel.2
#bit Analog_3 = Analog_Channel.3
#bit Analog_4 = Analog_Channel.4
#bit Analog_5 = Analog_Channel.5
#bit Analog_6 = Analog_Channel.6
#bit Analog_7 = Analog_Channel.7
Int1 Analog_Been_Initialized=0;
/***********************************************************
* Analog *
***********************************************************/
#inline
void Analog_Service(void)
{ if(!Analog_Been_Initialized)
{ setup_port_a( RA0_ANALOG );
setup_adc_ports( ALL_ANALOG );
setup_adc(ADC_CLOCK_INTERNAL);
Analog_Been_Initialized=1;
Analog_Channel=0;
}
else
{
if((Analog_Channel<<=1)==0)
Analog_Channel++; // Advance to the next channel
if(Analog_0)
{ set_adc_channel(0);
GO_DONE=1;
Analog_0_Work+=Analog_0_RAW;
Analog_0_Filtered=(Analog_0_Work>>2);
Analog_0_Work-=Analog_0_Filtered;
While(GO_DONE);
Analog_0_RAW=read_adc();
}
else if(Analog_1)
{ set_adc_channel(1);
GO_DONE=1;
Analog_1_Work+=Analog_1_RAW;
Analog_1_Filtered=(Analog_1_Work>>2);
Analog_1_Work-=Analog_1_Filtered;
While(GO_DONE);
Analog_1_RAW=read_adc();
}
else if(Analog_2)
{ set_adc_channel(2);
GO_DONE=1;
Analog_2_Work+=Analog_2_RAW;
Analog_2_Filtered=(Analog_2_Work>>2);
Analog_2_Work-=Analog_2_Filtered;
While(GO_DONE);
Analog_2_RAW=read_adc();
}
else if(Analog_3)
{ set_adc_channel(3);
GO_DONE=1;
Analog_3_Work+=Analog_3_RAW;
Analog_3_Filtered=(Analog_3_Work>>2);
Analog_3_Work-=Analog_3_Filtered;
While(GO_DONE);
Analog_3_RAW=read_adc();
}
else if(Analog_4)
{ set_adc_channel(4);
GO_DONE=1;
Analog_4_Work+=Analog_4_RAW;
Analog_4_Filtered=(Analog_4_Work>>2);
Analog_4_Work-=Analog_4_Filtered;
While(GO_DONE);
Analog_4_RAW=read_adc();
}
else if(Analog_5)
{ set_adc_channel(5);
GO_DONE=1;
Analog_5_Work+=Analog_5_RAW;
Analog_5_Filtered=(Analog_5_Work>>2);
Analog_5_Work-=Analog_5_Filtered;
While(GO_DONE);
Analog_5_RAW=read_adc();
}
else if(Analog_6)
{ set_adc_channel(6);
GO_DONE=1;
Analog_6_Work+=Analog_6_RAW;
Analog_6_Filtered=(Analog_6_Work>>2);
Analog_6_Work-=Analog_6_Filtered;
While(GO_DONE);
Analog_6_RAW=read_adc();
}
else if(Analog_7)
{ set_adc_channel(7);
GO_DONE=1;
Analog_7_Work+=Analog_7_RAW;
Analog_7_Filtered=(Analog_7_Work>>2);
Analog_7_Work-=Analog_7_Filtered;
While(GO_DONE);
Analog_7_RAW=read_adc();
}
}
}
|
|
|
|
future
Joined: 14 May 2004 Posts: 330
|
|
Posted: Thu Dec 02, 2004 1:51 pm |
|
|
Optimized a little:
Code: | switch (adch++) // 01xxx001
{ case 0:
adcon0=0b01000001; // set_adc_channel(0);
break;
case 1:
read_adc(ADC_START_ONLY);
break;
case 2:
adcraw[0]=adres;
adcon0=0b01001001; // set_adc_channel(1);
break;
case 3:
read_adc(ADC_START_ONLY);
break;
case 4:
adcraw[1]=adres;
adcon0=0b01010001; // set_adc_channel(2);
break;
case 5:
read_adc(ADC_START_ONLY);
break;
case 6:
adcraw[2]=adres;
adcon0=0b01011001; // set_adc_channel(3);
break;
case 7:
read_adc(ADC_START_ONLY);
break;
case 8:
adcraw[3]=adres;
adcon0=0b01100001; // set_adc_channel(4);
break;
case 9:
read_adc(ADC_START_ONLY);
break;
case 10:
adcraw[4]=adres;
adcon0=0b01101001; // set_adc_channel(5);
break;
case 11:
read_adc(ADC_START_ONLY);
break;
case 12:
adcraw[5]=adres;
adcon0=0b01110001; // set_adc_channel(6);
break;
case 13:
read_adc(ADC_START_ONLY);
break;
case 14:
adcraw[6]=adres;
adcon0=0b01111001; // set_adc_channel(7);
break;
case 15:
read_adc(ADC_START_ONLY);
break;
case 16:
adch=0;
adcraw[7]=adres;
break;
} |
Now I can work on an old idea, having both 10bit and 8bit channels working at the same time. |
|
|
future
Joined: 14 May 2004 Posts: 330
|
|
Posted: Tue Dec 07, 2004 5:14 pm |
|
|
Done, configure adc as 10bit and one can have 0~5v with 20mV resolution and 0~1.25v with 5mV resolution.
Code: | #define result_left() bit_clear(adcon1,7) // result left justified
#define result_right() bit_set(adcon1,7) // result right justified
adcon0=0b01100001; // channel(4);
if(highres==true)
{ result_right(); }
else { result_left(); }
break;
case 9:
read_adc(ADC_START_ONLY);
break;
case 10:
if(highres==true)
{ adcraw=adresl; } // 0~1.25v
else { adcraw=adresh; } // 0~5v |
|
|
|
djwallace
Joined: 26 Nov 2004 Posts: 10
|
Quick Question about ADCs |
Posted: Tue Jan 11, 2005 2:57 pm |
|
|
If I do not specify the line
#device adc=X
where X is the resolution. will it use 10Bit resolution? I am currently using a PIC16F88 with a 10 Bit ADC.
Thanx,
Darryl _________________ darryl |
|
|
|