CCS C Software and Maintenance Offers
FAQFAQ   FAQForum Help   FAQOfficial CCS Support   SearchSearch  RegisterRegister 

ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

CCS does not monitor this forum on a regular basis.

Please do not post bug reports on this forum. Send them to support@ccsinfo.com

the best way to read an analogue value ADC

 
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion
View previous topic :: View next topic  
Author Message
ralph79



Joined: 29 Aug 2007
Posts: 87

View user's profile Send private message

the best way to read an analogue value ADC
PostPosted: Mon Jan 14, 2008 3:04 pm     Reply with quote

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

View user's profile Send private message

PostPosted: Mon Jan 14, 2008 3:53 pm     Reply with quote

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

View user's profile Send private message

Thanks...
PostPosted: Tue Jan 15, 2008 8:16 am     Reply with quote

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

View user's profile Send private message

PostPosted: Tue Jan 15, 2008 8:33 am     Reply with quote

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







PostPosted: Tue Jan 15, 2008 8:38 am     Reply with quote

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

View user's profile Send private message

PostPosted: Wed Jan 16, 2008 4:41 am     Reply with quote

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







PostPosted: Wed Jan 16, 2008 5:06 am     Reply with quote

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








PostPosted: Tue Jan 22, 2008 7:52 am     Reply with quote

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







PostPosted: Tue Jan 22, 2008 10:46 am     Reply with quote

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

View user's profile Send private message

PostPosted: Tue Jan 22, 2008 12:32 pm     Reply with quote

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
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion All times are GMT - 6 Hours
Page 1 of 1

 
Jump to:  
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