|
|
View previous topic :: View next topic |
Author |
Message |
jecottrell
Joined: 16 Jan 2005 Posts: 559 Location: Tucson, AZ
|
Nuts and Bolts of ADC Conversions |
Posted: Wed Mar 28, 2007 7:55 pm |
|
|
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
|
|
Posted: Thu Mar 29, 2007 7:36 am |
|
|
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
|
|
Posted: Thu Mar 29, 2007 8:53 am |
|
|
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
|
|
Posted: Thu Mar 29, 2007 9:46 am |
|
|
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
|
|
Posted: Thu Mar 29, 2007 10:13 am |
|
|
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
|
|
Posted: Thu Mar 29, 2007 11:42 am |
|
|
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
|
|
Posted: Thu Mar 29, 2007 11:57 am |
|
|
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
|
|
Posted: Thu Mar 29, 2007 12:10 pm |
|
|
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
|
|
Posted: Thu Mar 29, 2007 12:41 pm |
|
|
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
|
|
Posted: Thu Mar 29, 2007 3:10 pm |
|
|
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
|
|
Posted: Thu Mar 29, 2007 3:25 pm |
|
|
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
|
|
Posted: Fri Mar 30, 2007 7:32 am |
|
|
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
|
|
Posted: Fri Mar 30, 2007 10:24 am |
|
|
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
|
|
Posted: Fri Mar 30, 2007 11:31 am |
|
|
Obvious comment. Check the ADC channel selection. Your 'comment' refers to AD0, but you are selecting AD1.
Best Wishes |
|
|
|
|
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
|