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

Nuts and Bolts of ADC Conversions

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



Joined: 16 Jan 2005
Posts: 559
Location: Tucson, AZ

View user's profile Send private message

Nuts and Bolts of ADC Conversions
PostPosted: Wed Mar 28, 2007 7:55 pm     Reply with quote

Hello All,

I'm trying to revise some code and would like to know the ins and outs of ADC reading. I previously used the INT_TIMER1 and INT_AD to precisely time my readings. In my revision I'd like to make sure I understand what I'm doing and do things right.

The biggest question I have is about what goes on during ADC reads. Can I start and ADC read on the timer interrupt and read it on the following interrupt?

Code:

pseudo code:

int_timer1
   get ADC value
   start ADC read


So, is the value that is read during the next Timer1 interrupt the value at that precise point in time or is it the value that was read precisely when the conversion was complete after starting the ADC read?

Can I get away without using the int_ad? (The timer1_int will be significantly longer than the ADC conversion.)

Or..... Do I have to try to time the completion of the ADC reads to be on my timing scheme?

Thanks,

John
rwyoung



Joined: 12 Nov 2003
Posts: 563
Location: Lawrence, KS USA

View user's profile Send private message Send e-mail

PostPosted: Thu Mar 29, 2007 7:36 am     Reply with quote

If the time between reads (your timer 1 tic I would assume) is greater than the minimum time it takes the PIC to complete an ADC conversion (consult datasheet) then the value you read back from the registers is a latched copy of the last completed conversion.

If the time between reads (timer tic is less than minimum time required) then you can either check the adc status bit or accept the value in the adc data registers, knowing that it may or may not be 100% correct.

You can use a static variable inside your ISR to help you keep track of the "freshness" of the ADC data. But what I would definately NOT do is sit-and-spin inside the ISR waiting on the ADC done bit.
_________________
Rob Young
The Screw-Up Fairy may just visit you but he has crashed on my couch for the last month!
jecottrell



Joined: 16 Jan 2005
Posts: 559
Location: Tucson, AZ

View user's profile Send private message

PostPosted: Thu Mar 29, 2007 8:53 am     Reply with quote

OK. That answers my question.

Re-hash... to ensure correct understanding:

Once an ADC read is requested, the conversion is started. After it is complete the value that was read is held until another read is requested.

Can you assume that readings will take the same amount of time, each time? So, can you start a read on a timer and finish the reading when the int_ad fires?

Or.. what is the 'recommended' way of timed ADC reads?

I've seen bits and pieces here in the forum, but is there a complete solution or example?

Thanks for all the help,

John
Ttelmah
Guest







PostPosted: Thu Mar 29, 2007 9:46 am     Reply with quote

There are three 'parts' to an 'ADC reading'. The first is triggering the adc, The second, waiting for it to do the conversion, the third is reading the result. The CCS function 'read_adc', called without a valuie passed to it, _performs all three automatically_. However provided your compiler is reasonably modern, the parts can be requested separately. So:

read_adc(ADC_READ_ONLY);

returns the value of the _last_ conversion requested.

read_adc(ADC_START_ONLY);

triggers a conversion, without waiting for it to complete, and without returning a value.

So, if in your timer interrupt, you call:
Code:


adval=read_adc(ADC_READ_ONLY); //get the value last read
read_adc(ADC_START_ONLY); //This will start the ADC, getting a value


The actual conversion, takes the number of ADC clock cycles for the chip concerned (normally 12 cycles for a 10bit ADC). With the duration of each cycle depending on the clock speed selected for the ADC. In general, for 'slower' PICs, the time needed to enter an interrupt routine, is longer than the time needed for the conversion, making this a wasteful way of working. Consider possibly getting rid of your timer interrupt!. You can trigger the ADC, from a CCP channel at an automatic interval, and will then receive an ADC interrupt when the conversion has completed. Use this interrupt, instead of the timer interrupt, and everything becomes synchronous, and you get rid of the overheads associated with multiple interrupt handlers.

Best Wishes
jecottrell



Joined: 16 Jan 2005
Posts: 559
Location: Tucson, AZ

View user's profile Send private message

PostPosted: Thu Mar 29, 2007 10:13 am     Reply with quote

RJ,

BTW, 18F6627 & 3.236 @ 40MHz

Thanks. That is a big help in understanding. I had read one of your previous posts with a a similar code snippet and that's what got me interested in cleaning up my sloppy approach.

Your suggested solution raises another question for me.

Quote:
You can trigger the ADC, from a CCP channel at an automatic interval, and will then receive an ADC interrupt when the conversion has completed. Use this interrupt, instead of the timer interrupt, and everything becomes synchronous, and you get rid of the overheads associated with multiple interrupt handlers.


So, does that mean to use some sort of external clock source to drive the CCP? I can't think of how you'd trigger the ADC from the CCP? Other than in another interrupt? And then aren't you back to using 2 interrupts again?

I'll try to do some reading and figure out if I can understand what you are suggesting.

Thanks again,

John
Ttelmah
Guest







PostPosted: Thu Mar 29, 2007 11:42 am     Reply with quote

You don't have to enable an interrupt from the CCP. One of the options on the CCP, is to have it trigger a 'special event'. This can be setup to be the
ADC conversion. You then just get the ADC interrupt when the conversion is complete. If the timing of the CCP can be set to a value to suit you, you can read the ADC value, then handle the 'time' events, all in the one handler.

Best Wishes
jecottrell



Joined: 16 Jan 2005
Posts: 559
Location: Tucson, AZ

View user's profile Send private message

PostPosted: Thu Mar 29, 2007 11:57 am     Reply with quote

OK. That sounds exactly like what I'd like to do.

I just made a quick read over of the Special Event feature of the CCP module. I'll try reading some more, but I can't see a couple of things and they aren't necessarily obvious to me because my unfamiliarity with the PIC. Can I use the special event feature with it not being connected? In other words can I use that feature of the module without any manifestations on pins and other things?

My general approach would be:

Setup CCP to fire on my desired timing
Setup special event feature to start the ADC
Write an AD ISR to read the completed AD reading

BTW, thanks for all the help... this is not an attempt to get you to spoon feed me. I just need a few questions answered here and there and I think I'll be able to figure it out.

John
Ttelmah
Guest







PostPosted: Thu Mar 29, 2007 12:10 pm     Reply with quote

Yes. Mode 11, gives a trigger, without driving the output pin. Even modes that do drive the pin, can be used, by programming the TRIS for the pin to '1', which makes it useable as an input.

Best Wishes
jecottrell



Joined: 16 Jan 2005
Posts: 559
Location: Tucson, AZ

View user's profile Send private message

PostPosted: Thu Mar 29, 2007 12:41 pm     Reply with quote

As I get deeper into this it gets more difficult.

It looks like I'll need to manually enable the special event mode. I can do that. I'll use:

Code:
   ccp2con_image = CCP2CON;
   ccp2con_image = ccp2con_image | 0b00001011;
   CCP2CON = ccp2con_image;


What is confusing me now is where does the CCP2 module get its timing from and what mode do I set the CCP up for? I can see in the datasheet that 11 is a compare mode. I just found the stuff about comparing CCPR2 to timer1 or timer3. So it sounds like I need to setup timer1 and put my desired timing value in CCPR2. After I start timer1, every time timer1 equals CCPR2 the ADC will start. When the int_ad fires I read the value....

I would assume I don't want a timer1 ISR, nor do I want to enable the timer1 interrupt.

Am I getting closer?

Thanks,

John


EDIT:

Here is more of what I've got so far:

Code:
////////////////////////////////////////////////////////////////////////////////
//     CCP AD Support
////////////////////////////////////////////////////////////////////////////////
#byte   CCP2CON     = 0xFBA
#byte   CCPR2L      = 0xFBB
#byte   CCPR2H      = 0xFBC
int8    ccp2con_image;
int16   ccpr2_val   = 39062;  // @ 40MHz, 1/10,000,000 seconds per count
                              // need to fire every 0.0039062 seconds
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
//            AD ISR
////////////////////////////////////////////////////////////////////////////////
#int_AD
void AD_isr(void) {
   MCU_ADC_AN = read_adc(ADC_READ_ONLY);   //Put current ADC reading in variable
}


void config_ccp_for_ad(void)
{
   //NOT SURE ABOUT THIS.....
   setup_ccp2(??)
   ccp2con_image = CCP2CON;
   ccp2con_image = ccp2con_image | 0b00001011;
   CCP2CON = ccp2con_image;
   //set compare value
   CCPR2L = ccpr2_val;
   CCPR2H = ccpr2_val >> 8;
   //enable timer1
   setup_timer_1(T1_INTERNAL | T1_DIV_BY_1);
}
Ttelmah
Guest







PostPosted: Thu Mar 29, 2007 3:10 pm     Reply with quote

You don't have to set things up yourself. The CCS functions support this. It is 'CCP_COMPARE_RESET_TIMER'. 'OR' this with 'CCP_USE_TIMER3', if you want to use timer3, but otherwise it'll use timer1. Your timing is dependant on the clock selected for this timer, and the value used in the compare (remember it counts 0...compare value, then restarts at 0. So for a compare of '199', you get 200 counts). The timer will never interrupt, since it gets reset before reaching the terminal count. You will get a CCP interrupt,when the comparison ocurs (which you just leave disabled, and ignore), and a ADC interrupt when the conversion completes. You just set the CCP register (CCP_1, or CCP_2), to the required count.
The limit, is on the lowest rate possible with the standard clock. If you want a really slow count, then as you mentioned before, drive Timer1, or Timer3, off an external source (but this costs pins).

Best Wishes
rnielsen



Joined: 23 Sep 2003
Posts: 852
Location: Utah

View user's profile Send private message

PostPosted: Thu Mar 29, 2007 3:25 pm     Reply with quote

I, personally, like to do things manually instead of having the built-in functions control the ADC procedure. I'll declare the individual bits, like GO_DONE(which is the ADC status bit), and have one of the timers set that bit when it's time. When the GO_DONE bit is set it will start the ADC on it's way.

Then, I do an if(!GO_DONE) evaluation(in my main() body) and when the GO_DONE bit clears that means the ADC is finished. My code then enters that if() statement and reads the registers. I don't need to worry about timing because the hardware will clear the GO_DONE bit when it's finished the conversion.

Ronald
jecottrell



Joined: 16 Jan 2005
Posts: 559
Location: Tucson, AZ

View user's profile Send private message

PostPosted: Fri Mar 30, 2007 7:32 am     Reply with quote

RJ,

I've got the following and the ADC is returning all zeros. Could you take a look and see if you see anything obvious. It looks like everything else is working... The sample array is getting filled, etc.

Code:
////////////////////////////////////////////////////////////////////////////////
//     CCP AD Support
////////////////////////////////////////////////////////////////////////////////
#byte   CCP2CON     = 0xFBA
int8    ccp2con_image;
int16   ccpr2_val   = 39062;  // @ 40MHz, 1/10,000,000 seconds per count
                              // need to fire every 0.0039062 seconds
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
//            AD ISR
////////////////////////////////////////////////////////////////////////////////
#int_AD
void AD_isr(void) {
   MCU_ADC_AN = read_adc(ADC_READ_ONLY);   //Put current ADC reading in variable
   ADC_TIMER_FLAG = TRUE;                  //
}
////////////////////////////////////////////////////////////////////////////////

   /////////////////////////////////////////////////////////////////////////////
   //setup ccp and analogs for sampling
   /////////////////////////////////////////////////////////////////////////////
   setup_adc_ports(AN0_TO_AN1|VSS_VDD);
   setup_adc(ADC_CLOCK_DIV_32);
   set_adc_channel(1);            //select RA0 for ADC input
   delay_us(10);
   read_adc(ADC_START_ONLY);
   //configure ccp module
   setup_ccp2(CCP_COMPARE_RESET_TIMER | CCP_USE_TIMER3);
   //set compare value
   CCP_2_LOW  = ccpr2_val;
   CCP_2_HIGH = ccpr2_val >> 8;
   //enable timer1
   setup_timer_3(T3_INTERNAL | T3_DIV_BY_1);

   enable_interrupts(INT_AD);
   /////////////////////////////////////////////////////////////////////////////


Ronald,

Thanks for the ideas. I've started to prefer tweaking the bits on my own also. Mainly because I get a better understanding for what's happening. If I'm unsuccessful with my current approach I'll try yours.

Thanks again to all,

John
jecottrell



Joined: 16 Jan 2005
Posts: 559
Location: Tucson, AZ

View user's profile Send private message

PostPosted: Fri Mar 30, 2007 10:24 am     Reply with quote

I got it working. It wasn't a setup problem. It was a variable problem that got overlooked in the porting to the new method of conversion.

A big thanks to all for the assistance,

John
Ttelmah
Guest







PostPosted: Fri Mar 30, 2007 11:31 am     Reply with quote

Obvious comment. Check the ADC channel selection. Your 'comment' refers to AD0, but you are selecting AD1.

Best Wishes
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