|
|
View previous topic :: View next topic |
Author |
Message |
ralph79
Joined: 29 Aug 2007 Posts: 87
|
the best way to read an analogue value ADC |
Posted: Mon Jan 14, 2008 3:04 pm |
|
|
I have a very simple question,
I must read an analogue value several times and I get several different values with variations of 10 to 50 between the values.
In order words sometimes I get 220 but some time after I get 260 but the analogue value is the same...
My code is this:
Code: |
#include <16F883.h>
#device *=16 // FULL RAM ACCESS
#device adc = 10 // ADC de 10 bits
#FUSES NOWDT //No Watch Dog Timer
#FUSES INTRC_IO //Internal RC Osc, no CLKOUT
#FUSES PUT //Power Up Timer
#FUSES NOMCLR //No Master Clear pin enabled
#FUSES NOPROTECT //No Code protected from reads
#FUSES NOCPD //No Data EEPROM Code Protected
#FUSES NOBROWNOUT //No brownout reset
#FUSES NOIESO //No Internal External Switch Over mode enabled
#FUSES NOFCMEN //Fail-safe clock monitor enabled
#FUSES NOLVP //No low voltage prgming, B3(PIC16) or B5(PIC18) used for I/O
#FUSES DEBUG //Debug mode for use with ICD
#FUSES NOWRT //No Program Memory Write Protected
#FUSES BORV40 //Brownout reset at 4.0V
#use delay(clock = 8000000)
#use rs232(baud = 57600, parity = N, xmit = PIN_C6, rcv = PIN_C7, bits = 8)
#priority EXT, TIMER1
int16 adc_value = 0x0000;
int1 read_adc = 0;
#INT_EXT
void EXT_isr(void)
{
//...
}
#int_TIMER1
void TIMER1_isr(void)
{
//....
read_adc = 1;
}
#int_TIMER0
void TIMER0_isr(void)
{
//....
}
void Read_Valor_ADC(void)
{
int8 i = 0;
int16 Val_ADC_Acum = 0;
disable_interrupts(GLOBAL);
for (i = 0; i < 5; i++)
{
read_adc(ADC_START_ONLY);
delay_us(10);
Val_ADC_Acum += read_adc(ADC_READ_ONLY);
delay_us(10);
}
adc_value = (Val_ADC_Acum/5) + 1;
enable_interrupts(GLOBAL);
}
void main(void)
{
setup_spi(SPI_SS_DISABLED);
setup_comparator(NC_NC_NC_NC);
//--------------- ADC -----------------//
setup_adc_ports(sAN1|VSS_VDD);
setup_adc(ADC_CLOCK_DIV_8);
set_adc_channel(1);
//--------------- TIMER -----------------//
setup_timer_0(RTCC_INTERNAL|RTCC_DIV_256); // DIV_1 = 128us; DIV_2 = 256us; DIV_4 = 512us; DIV_8 = 1ms; DIV_16 = 2ms; DIV_32 = 4ms; DIV_64 = 8.1ms; DIV_128 = 16.3ms; DIV_256 = 32.7ms
setup_timer_1(T1_INTERNAL|T1_DIV_BY_4); // DIV_BY_1 = 32.7ms; DIV_BY_2 = 65.5ms; DIV_BY_4 = 131ms; DIV_BY_8 = 262ms
setup_timer_2(T2_DIV_BY_1,99,1); // pwm a 40KHZ
//----------INTERRUPCAO----------------//
clear_interrupt(INT_TIMER0);
clear_interrupt(INT_TIMER1);
clear_interrupt(INT_EXT);
ext_int_edge(L_TO_H);
enable_interrupts(INT_EXT);
enable_interrupts(INT_TIMER0);
enable_interrupts(INT_TIMER1);
enable_interrupts(GLOBAL);
setup_oscillator(OSC_8MHZ);
void main(void)
{
while(TRUE)
{
if (read_adc == 1)
{
read_adc = 0;
Read_Valor_ADC();
printf("adc_value = "+0x00+"\n");
}
}
}
}
|
Am I doing something wrong?
There is a better way to get the analogue value?
Best regards, |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Mon Jan 14, 2008 3:53 pm |
|
|
Try the test program shown below. I tried it with a 16F886 and
it produced a very stable output from the A/D converter. I tested
this on a PicDem2-Plus board, and compiled it with vs. 4.065.
I don't have a 16F883, so I used a 16F886, which is a very similar PIC.
Code: |
#include <16F883.H>
#device adc=10
#fuses INTRC_IO, NOWDT, PROTECT, BROWNOUT, PUT, NOLVP
#use delay(clock=8000000)
#use rs232(baud=9600, xmit=PIN_C6, rcv=PIN_C7, ERRORS)
//====================================
void main()
{
int16 result;
setup_adc_ports(sAN0);
setup_adc(ADC_CLOCK_DIV_32);
set_adc_channel(1);
delay_us(20);
while(1)
{
result = read_adc();
printf("%lu \n\r", result);
delay_ms(500);
}
} |
|
|
|
ralph79
Joined: 29 Aug 2007 Posts: 87
|
Thanks... |
Posted: Tue Jan 15, 2008 8:16 am |
|
|
With your code I have much more reliability than with my code.
I've thought that it was better first to start the ADC and shortly after read the value.
I've also used the ADC_CLOCK_DIV_8 option because I thought that it was better to have a slower clock.
Thanks PCM Programmer...
Regards |
|
|
ckielstra
Joined: 18 Mar 2004 Posts: 3680 Location: The Netherlands
|
|
Posted: Tue Jan 15, 2008 8:33 am |
|
|
Quote: | I've also used the ADC_CLOCK_DIV_8 option because I thought that it was better to have a slower clock. | The ADC_CLOCK_DIV_8 option is faster than the ADC_CLOCK_DIV_32 option, not slower. A slower clock is not always better than a faster clock, when meeting the required minimum conversion time a slower clock is just as good but wasting more time.
Always check the datasheet! Table 9-1: At 8MHz your selection of ADC_CLOCK_DIV_8 is too fast and will result in bad conversions.
Besides the programmatic side of A/D conversion it is important your analog source has an impedance smaller than 10k Ohm (datasheet Table 17-9). Sometimes this requires the addition of an external voltage buffer (opamp). |
|
|
Ttelmah Guest
|
|
Posted: Tue Jan 15, 2008 8:38 am |
|
|
The test program, uses ADC_DIV_32, which gives a clock 4* slower than your program.
Your crystal is 8MHz. If you look at table 9-1 in the data sheet, the _maximum_ crystal rate, that gives a legitimate timing with ADC_DIV_8, is 4Mhz. This is probably why the test program works better than yours....
You cannot take the reading from the ADC, till the conversion is complete. The standard function, starts the ADC, waits for the conversion to complete, then reads the value. The _best_ way to do it, is to clear the adc interrupt, enable this interrupt (with the global interrupts disabled), start the ADC conversion, and then put the processor to sleep. Then when it awakens, read the ADC. This gives the least noise possible for the conversion. Your version is no better, and takes longer (the standard code simply waits for the flag to say that the conversion has completed).
So:
Code: |
disable_interrupts(GLOBAL);
enable_interrupts(INT_ADC);
clear_interrupts(INT_ADC);
read_adc(ADC_START_ONLY);
sleep();
delay_cycles(1);
val=read_adc(ADC_READ_ONLY;
disable_interrupts(INT_ADC);
enable_interrupts(GLOBAL); //remove if you are not using interrupts
|
This stops quite a few parts of the CPU, during the conversion, and gives the best results.
Best Wishes |
|
|
ralph79
Joined: 29 Aug 2007 Posts: 87
|
|
Posted: Wed Jan 16, 2008 4:41 am |
|
|
Hi Ttelmah
The way I have my application, with the PCM code I have no problem, but with yours it seems that the application freezes.
I have several variables and functions running each time. The two timers and the external interrupt gives orders to my main function.
In some conditions, from 32.7 in 32.7ms I must read the analogue value. These should as fast as possible and with the best accuracy. So that if it is 130% higher than value obtained in the start of the application it must do something, but if it lower than 50% of this value, it should do other thing completely different.
For now I will be testing the PCM code, if you have another approach please let me know
Thanks |
|
|
Ttelmah Guest
|
|
Posted: Wed Jan 16, 2008 5:06 am |
|
|
Sorry, one line left out of my code.
You need to set the ADC, to use the RC clock source, if you are going to sleep wth it. Otherwise the clock will stop when you sleep. I'm so used to doing this, that I forgot to mention it!...
If you are using th external interrupt, then this should be _disabled_ before this code (otherwise this too can wake the chip, and will result in the reading being invalid). Obviously re-enabled immediately afterwards.
The 'sleep' code, _will_give fractionally more accurate readings. In order of 'bad to good', the accuracy will run:
1) Your code (clock too fast).
2) PCM type code, with the interrupts left enabled during the reading.
3) PCM type code, with the interrupts disabled during the reading.
4) Sleep code.
The 'sleep' code, will be _slightly faster_ than the ADC_CLOCK_DIV_32 code, despite the extra instructions involved.
Best Wishes |
|
|
Guest
|
|
Posted: Tue Jan 22, 2008 7:52 am |
|
|
I've been out in bed for several days...with a flew...
I've tried Ttelmah code and I cant get it to work.
My Ttelmah' code version is:
Code: |
disable_interrupts(GLOBAL);
setup_adc(ADC_OFF); // i've tried also with ADC_INTERNAL, ADC_CLOCK_DIV_32 and all other possibilities, with no luck
enable_interrupts(INT_AD);
clear_interrupt(INT_AD);
read_adc(ADC_START_ONLY);
sleep();
delay_cycles(1);
Val_ADC_Acum += read_adc(ADC_READ_ONLY);
disable_interrupts(INT_AD);
enable_interrupts(GLOBAL); //remove if you are not using interrupts
|
for now my code is:
Code: |
disable_interrupts(GLOBAL);
Valor_ADC_Acum += read_adc();
enable_interrupts(GLOBAL);
|
But I'm looking a better solution
Best regards, |
|
|
Ttelmah Guest
|
|
Posted: Tue Jan 22, 2008 10:46 am |
|
|
Is this 'for real', or in a simulator?.
Just tried:
Code: |
void main() {
int16 result;
setup_adc_ports(sAN0);
setup_adc(ADC_CLOCK_INTERNAL);
set_adc_channel(0);
delay_us(20);
while(1) {
disable_interrupts(GLOBAL);
enable_interrupts(INT_AD);
clear_interrupt(INT_AD);
read_adc(ADC_START_ONLY);
sleep();
delay_cycles(1);
result= read_adc(ADC_READ_ONLY);
disable_interrupts(INT_AD);
enable_interrupts(GLOBAL);
printf("%lu \n\r", result);
delay_ms(500);
}
}
|
In a 18F1220, and it ran fine.
Tried the same in MPLAB simulator for the 16F883, and it hung...
Best Wishes |
|
|
ralph79
Joined: 29 Aug 2007 Posts: 87
|
|
Posted: Tue Jan 22, 2008 12:32 pm |
|
|
I've trying for real...
And I've been killing my head with some errors that I don't know who is the fault, if mine, if the 16F883 (I've tried several 16F883) or even of the compiler..
For example I'm reading the internal with the CCS built in function (read_eeprom) and sometime I get very different values than the one I have the micro eeprom.
And I don't know why is this happening, do you have any ideas?
Best regards |
|
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|