EmbeddedRelated.com
Forums
Memfault Beyond the Launch

LPC2138 ADC Question

Started by fitzdean April 22, 2008
We are having strange behavior with our ADC code.
In many of the examples I have seen in this forum,
there seems to be a strong trend toward the following
sequence of events:

*PCONP and PINSELx properly set up
*ADC control register written to with
everything set up but conversion not started
*Start conversion bit or'd into the control
register.
*Wait for DONE
*When DONE received, clear the start conversion bit
in the control register.

Is there some undocumented mystery on why the
control register must first be initialized without
the start conversion bit, and then re-written with
the start conversion bit for it to work correctly?

The last part of the sequence, where the start
conversion bit is cleared shows up in 70% of the
coding examples. Is that just a good habit, or
another of those "lessons learned on the LPC2138"?

Thanks in advance.

-Dean

An Engineer's Guide to the LPC2100 Series

I'll start with the easy question:
> Is there some undocumented mystery on why the
> control register must first be initialized without
> the start conversion bit, and then re-written with
> the start conversion bit for it to work correctly?
This is probably just to give a bit of time for the analog signal
levels to settle before starting the conversion.
One thing you can change in the control register is _which_
of the input pins is routed to the ADC.
Once that routing is set up, the 20 kOhm resistor in figure
55 of the user manual links the appropriate input pin to the
ADC sample point, and you then have to allow the 3 pF capacitor
to charge since otherwise your reading will partly depend on
what was previously on the capacitor.
The time constant is short (to 1% you need 5 x C x R = 300 ns).

Anyone who previously worked on PIC microcontrollers will
probably know that the equivalent time constant there can
be much larger.

The other one might be a case of "knowing no better"
> The last part of the sequence, where the start
> conversion bit is cleared shows up in 70% of the
> coding examples. Is that just a good habit, or
> another of those "lessons learned on the LPC2138"?
The user manual does not explicitly state that this bit will be
cleared on completion of the conversion, nor does it state
whether conversion starts when the bit is changed from 0 to 1
or whenever it is written as 1.
Most people stop changing the code when it seems to work...

Does anyone know any better?
- Danish
--- In l..., "fitzdean" wrote:
>
> We are having strange behavior with our ADC code.
> In many of the examples I have seen in this forum,
> there seems to be a strong trend toward the following
> sequence of events:
>
> *PCONP and PINSELx properly set up
> *ADC control register written to with
> everything set up but conversion not started
> *Start conversion bit or'd into the control
> register.
> *Wait for DONE
> *When DONE received, clear the start conversion bit
> in the control register.

Good Questions Dean!
I must admit that I don't know ether!
I thought I post an email of support just in case some LPC guru decides the
question is 'too simple' to bother answering ...

What at your 'strange problems'? Below is my code for the LKPOC2378 that
works for me.

#define ADC0CR_DEF
BITS32(_0000,_0000,_0010,_0000,_0000,_0100,_0000,_0000)
#define ADC_START (0x01000000) // mask to
write to ADxCR reg to start one ADC conversion
#define ADC_CHAN(x) (1<<(x)) // macro to
define the ADC channel to select (x=0-7)

//--
----
// ADxGDR bit values
#define ADC_DONE (BIT(31)) // set the ADC
conversion if complete
#define ADC_VAL(r) ((uint16_t)(((r)>>6) & 0x3ff)) //
given raw ADxGDR value returns the 10 bit adc value

void ADC0_Init(void)
{
uint32_t tmp;
// powerup the ADC
PCONP |= PCONP_PCAD;

// Select AD0.0(15:14) AD0.1(17:16) AD0.2(19:18)
AD0.3(21:20)
tmp = PINSEL1;
tmp &= ~(BIT(15)|BIT(17)|BIT(19)|BIT(21));
tmp |= (BIT(14)|BIT(16)|BIT(18)|BIT(20));
PINSEL1 = tmp ;

// Select AD0.5(31:30)
tmp = PINSEL3;
tmp |= (BIT(31)|BIT(30));
PINSEL3 = tmp ;

AD0CR = ADC0CR_DEF ;

return;
}

uint16_t ADC0_Read(uint8_t ch)
{
uint16_t adc_val;
uint32_t tmp;

assert(ch<8);

tmp = AD0CR;
tmp &= ~AD0CR_SEL_MASK; // clear out Channel SEL bits
tmp |= ADC_CHAN(ch); // Select channel

tmp &= ~AD0CR_START_MASK; // clear out start bits
tmp |= ADC_START; // start adc conversion
AD0CR= tmp; // write it! => begin
conversion

// Wait until conversion completed
do
{
WATCHDOG();
tmp = AD0GDR;
}
while ((tmp&ADC_DONE)==0);

AD0CR &= ~ADC_START; // stop adc conversion
adc_val = ADC_VAL(tmp); // convert to 10 bit value
return(adc_val);
}
HTH
Ivan Vernot
> -----Original Message-----
> From: l...
> [mailto:l...] On Behalf Of fitzdean
> Sent: Wednesday, 23 April 2008 1:25 AM
> To: l...
> Subject: [lpc2000] LPC2138 ADC Question
>
> We are having strange behavior with our ADC code.
> In many of the examples I have seen in this forum, there
> seems to be a strong trend toward the following sequence of events:
>
> *PCONP and PINSELx properly set up
> *ADC control register written to with
> everything set up but conversion not started *Start
> conversion bit or'd into the control register.
> *Wait for DONE
> *When DONE received, clear the start conversion bit in the
> control register.
>
> Is there some undocumented mystery on why the control
> register must first be initialized without the start
> conversion bit, and then re-written with the start conversion
> bit for it to work correctly?
>
> The last part of the sequence, where the start conversion bit
> is cleared shows up in 70% of the coding examples. Is that
> just a good habit, or another of those "lessons learned on
> the LPC2138"?
>
> Thanks in advance.
>
> -Dean
>
Hi Ivan,

Thanks for the advice.
I see you code also stops the conversion after the
DONE bit is detected. Is there a known reason for
that, or just a lesson learned?

Also, be VERY careful of code like:

PCONP |= PCONP_PCAD;

When the PCONP register (or any LPC21XX register) is read
to "OR" back in the PCONP_PCAD bit, there are reserved bits
that have undefined results when reading them. Which means
if you read a reserved bit as a "1", you would write it back
as a "1" and according to the manufacturer's specification,
that is not to be done. FYI

--- In l..., "Ivan Vernot" wrote:
>
> Good Questions Dean!
> I must admit that I don't know ether!
> I thought I post an email of support just in case some LPC guru
decides the
> question is 'too simple' to bother answering ...
>
> What at your 'strange problems'? Below is my code for the LKPOC2378
that
> works for me.
>
> #define ADC0CR_DEF
> BITS32(_0000,_0000,_0010,_0000,_0000,_0100,_0000,_0000)
> #define ADC_START (0x01000000) //
mask to
> write to ADxCR reg to start one ADC conversion
> #define ADC_CHAN(x) (1<<(x)) //
macro to
> define the ADC channel to select (x=0-7)
>
> //------------------------------
--------
> ----
> // ADxGDR bit values
> #define ADC_DONE (BIT(31)) // set the ADC
> conversion if complete
> #define ADC_VAL(r) ((uint16_t)(((r)>>6) & 0x3ff))
//
> given raw ADxGDR value returns the 10 bit adc value
>
> void ADC0_Init(void)
> {
> uint32_t tmp;
> // powerup the ADC
> PCONP |= PCONP_PCAD;
>
> // Select AD0.0(15:14) AD0.1(17:16) AD0.2(19:18)
> AD0.3(21:20)
> tmp = PINSEL1;
> tmp &= ~(BIT(15)|BIT(17)|BIT(19)|BIT(21));
> tmp |= (BIT(14)|BIT(16)|BIT(18)|BIT(20));
> PINSEL1 = tmp ;
>
> // Select AD0.5(31:30)
> tmp = PINSEL3;
> tmp |= (BIT(31)|BIT(30));
> PINSEL3 = tmp ;
>
> AD0CR = ADC0CR_DEF ;
>
> return;
> }
>
> uint16_t ADC0_Read(uint8_t ch)
> {
> uint16_t adc_val;
> uint32_t tmp;
>
> assert(ch<8);
>
> tmp = AD0CR;
> tmp &= ~AD0CR_SEL_MASK; // clear out Channel SEL bits
> tmp |= ADC_CHAN(ch); // Select channel
>
> tmp &= ~AD0CR_START_MASK; // clear out start bits
> tmp |= ADC_START; // start adc
conversion
> AD0CR= tmp; // write it! => begin
> conversion
>
> // Wait until conversion completed
> do
> {
> WATCHDOG();
> tmp = AD0GDR;
> }
> while ((tmp&ADC_DONE)==0);
>
> AD0CR &= ~ADC_START; // stop adc conversion
> adc_val = ADC_VAL(tmp); // convert to 10 bit value
> return(adc_val);
> }
> HTH
> Ivan Vernot
> > -----Original Message-----
> > From: l...
> > [mailto:l...] On Behalf Of fitzdean
> > Sent: Wednesday, 23 April 2008 1:25 AM
> > To: l...
> > Subject: [lpc2000] LPC2138 ADC Question
> >
> > We are having strange behavior with our ADC code.
> > In many of the examples I have seen in this forum, there
> > seems to be a strong trend toward the following sequence of
events:
> >
> > *PCONP and PINSELx properly set up
> > *ADC control register written to with
> > everything set up but conversion not started *Start
> > conversion bit or'd into the control register.
> > *Wait for DONE
> > *When DONE received, clear the start conversion bit in the
> > control register.
> >
> > Is there some undocumented mystery on why the control
> > register must first be initialized without the start
> > conversion bit, and then re-written with the start conversion
> > bit for it to work correctly?
> >
> > The last part of the sequence, where the start conversion bit
> > is cleared shows up in 70% of the coding examples. Is that
> > just a good habit, or another of those "lessons learned on
> > the LPC2138"?
> >
> > Thanks in advance.
> >
> > -Dean
>
Danish,

Great advice. THANKS!!!

-Dean

--- In l..., "Danish Ali" wrote:
>
> I'll start with the easy question:
> > Is there some undocumented mystery on why the
> > control register must first be initialized without
> > the start conversion bit, and then re-written with
> > the start conversion bit for it to work correctly?
> This is probably just to give a bit of time for the analog signal
> levels to settle before starting the conversion.
> One thing you can change in the control register is _which_
> of the input pins is routed to the ADC.
> Once that routing is set up, the 20 kOhm resistor in figure
> 55 of the user manual links the appropriate input pin to the
> ADC sample point, and you then have to allow the 3 pF capacitor
> to charge since otherwise your reading will partly depend on
> what was previously on the capacitor.
> The time constant is short (to 1% you need 5 x C x R = 300 ns).
>
> Anyone who previously worked on PIC microcontrollers will
> probably know that the equivalent time constant there can
> be much larger.
>
> The other one might be a case of "knowing no better"
> > The last part of the sequence, where the start
> > conversion bit is cleared shows up in 70% of the
> > coding examples. Is that just a good habit, or
> > another of those "lessons learned on the LPC2138"?
> The user manual does not explicitly state that this bit will be
> cleared on completion of the conversion, nor does it state
> whether conversion starts when the bit is changed from 0 to 1
> or whenever it is written as 1.
> Most people stop changing the code when it seems to work...
>
> Does anyone know any better?
> - Danish
> --- In l..., "fitzdean" wrote:
> >
> > We are having strange behavior with our ADC code.
> > In many of the examples I have seen in this forum,
> > there seems to be a strong trend toward the following
> > sequence of events:
> >
> > *PCONP and PINSELx properly set up
> > *ADC control register written to with
> > everything set up but conversion not started
> > *Start conversion bit or'd into the control
> > register.
> > *Wait for DONE
> > *When DONE received, clear the start conversion bit
> > in the control register.
>

fitzdean,

Your warning regading code like:

PCONP |= PCONP_PCAD;

is not valid as long as the argument specifies bits that are defined.
Your specific example is completely safe to use as the only bit that
can be set as a result is the PCONP_PCAD bit.

Jeff
--- In l..., "fitzdean" wrote:
>
> Hi Ivan,
>
> Thanks for the advice.
> I see you code also stops the conversion after the
> DONE bit is detected. Is there a known reason for
> that, or just a lesson learned?
>
> Also, be VERY careful of code like:
>
> PCONP |= PCONP_PCAD;
>
> When the PCONP register (or any LPC21XX register) is read
> to "OR" back in the PCONP_PCAD bit, there are reserved bits
> that have undefined results when reading them. Which means
> if you read a reserved bit as a "1", you would write it back
> as a "1" and according to the manufacturer's specification,
> that is not to be done. FYI
>
> --- In l..., "Ivan Vernot" wrote:
> >
> > Good Questions Dean!
> > I must admit that I don't know ether!
> > I thought I post an email of support just in case some LPC guru
> decides the
> > question is 'too simple' to bother answering ...
> >
> > What at your 'strange problems'? Below is my code for the
LKPOC2378
> that
> > works for me.
> >
> > #define ADC0CR_DEF
> > BITS32(_0000,_0000,_0010,_0000,_0000,_0100,_0000,_0000)
> > #define ADC_START (0x01000000) //
> mask to
> > write to ADxCR reg to start one ADC conversion
> > #define ADC_CHAN(x) (1<<(x))
//
> macro to
> > define the ADC channel to select (x=0-7)
> >
> > //----------------------------
--
> --------
> > ----
> > // ADxGDR bit values
> > #define ADC_DONE (BIT(31)) // set the ADC
> > conversion if complete
> > #define ADC_VAL(r) ((uint16_t)(((r)>>6) & 0x3ff))
> //
> > given raw ADxGDR value returns the 10 bit adc value
> >
> > void ADC0_Init(void)
> > {
> > uint32_t tmp;
> > // powerup the ADC
> > PCONP |= PCONP_PCAD;
> >
> > // Select AD0.0(15:14) AD0.1(17:16) AD0.2(19:18)
> > AD0.3(21:20)
> > tmp = PINSEL1;
> > tmp &= ~(BIT(15)|BIT(17)|BIT(19)|BIT(21));
> > tmp |= (BIT(14)|BIT(16)|BIT(18)|BIT(20));
> > PINSEL1 = tmp ;
> >
> > // Select AD0.5(31:30)
> > tmp = PINSEL3;
> > tmp |= (BIT(31)|BIT(30));
> > PINSEL3 = tmp ;
> >
> > AD0CR = ADC0CR_DEF ;
> >
> > return;
> > }
> >
> > uint16_t ADC0_Read(uint8_t ch)
> > {
> > uint16_t adc_val;
> > uint32_t tmp;
> >
> > assert(ch<8);
> >
> > tmp = AD0CR;
> > tmp &= ~AD0CR_SEL_MASK; // clear out Channel SEL bits
> > tmp |= ADC_CHAN(ch); // Select channel
> >
> > tmp &= ~AD0CR_START_MASK; // clear out start bits
> > tmp |= ADC_START; // start adc
> conversion
> > AD0CR= tmp; // write it! => begin
> > conversion
> >
> > // Wait until conversion completed
> > do
> > {
> > WATCHDOG();
> > tmp = AD0GDR;
> > }
> > while ((tmp&ADC_DONE)==0);
> >
> > AD0CR &= ~ADC_START; // stop adc conversion
> > adc_val = ADC_VAL(tmp); // convert to 10 bit value
> > return(adc_val);
> > }
> > HTH
> > Ivan Vernot
> >
> >
> > > -----Original Message-----
> > > From: l...
> > > [mailto:l...] On Behalf Of fitzdean
> > > Sent: Wednesday, 23 April 2008 1:25 AM
> > > To: l...
> > > Subject: [lpc2000] LPC2138 ADC Question
> > >
> > > We are having strange behavior with our ADC code.
> > > In many of the examples I have seen in this forum, there
> > > seems to be a strong trend toward the following sequence of
> events:
> > >
> > > *PCONP and PINSELx properly set up
> > > *ADC control register written to with
> > > everything set up but conversion not started *Start
> > > conversion bit or'd into the control register.
> > > *Wait for DONE
> > > *When DONE received, clear the start conversion bit in the
> > > control register.
> > >
> > > Is there some undocumented mystery on why the control
> > > register must first be initialized without the start
> > > conversion bit, and then re-written with the start conversion
> > > bit for it to work correctly?
> > >
> > > The last part of the sequence, where the start conversion bit
> > > is cleared shows up in 70% of the coding examples. Is that
> > > just a good habit, or another of those "lessons learned on
> > > the LPC2138"?
> > >
> > > Thanks in advance.
> > >
> > > -Dean
> > >
>
> -----Original Message-----
> From: l...
> [mailto:l...]On Behalf
> Of ksdoubleshooter
> Sent: Wednesday, April 23, 2008 11:38 PM
> To: l...
> Subject: [lpc2000] Re: LPC2138 ADC Question
> fitzdean,
>
> Your warning regading code like:
>
> PCONP |= PCONP_PCAD;
>
> is not valid as long as the argument specifies bits that are defined.
> Your specific example is completely safe to use as the only bit that
> can be set as a result is the PCONP_PCAD bit.
>
> Jeff
>

I believe in this case you are incorrect. Since the or operator
requires doing a read modify write operation, if PCONP contains
reserved bits, they will be read fist, and then written to. The
catch is that the read value of reserved bits is undefined, and
usually you are not supposed to write ones to them. So if the
read returns one, then the write will write a one.

To correct this one would do the following instead:
PCONP = (PCONP & !RESERVEDBITS) | PCONP_PCAD;
Where RESERVEDBITS is the bit mask of all of the reserved bits
for the PCONP register.

Mike
>
> I believe in this case you are incorrect. Since the or operator
> requires doing a read modify write operation, if PCONP contains
> reserved bits, they will be read fist, and then written to. The
> catch is that the read value of reserved bits is undefined, and
> usually you are not supposed to write ones to them. So if the
> read returns one, then the write will write a one.
>
> To correct this one would do the following instead:
> PCONP = (PCONP & !RESERVEDBITS) | PCONP_PCAD;
> Where RESERVEDBITS is the bit mask of all of the reserved bits
> for the PCONP register.
>
> Mike
>

I second that.

But I think you meant to use ~ instead of ! as follows: :-)
PCONP = (PCONP & ~RESERVEDBITS) | PCONP_PCAD;

Alan

> -----Original Message-----
> From: l...
> [mailto:l...]On Behalf
> Of lpc2xxx
> Sent: Friday, April 25, 2008 1:32 AM
> To: l...
> Subject: [lpc2000] Re: LPC2138 ADC Question
> >
> > I believe in this case you are incorrect. Since the or operator
> > requires doing a read modify write operation, if PCONP contains
> > reserved bits, they will be read fist, and then written to. The
> > catch is that the read value of reserved bits is undefined, and
> > usually you are not supposed to write ones to them. So if the
> > read returns one, then the write will write a one.
> >
> > To correct this one would do the following instead:
> > PCONP = (PCONP & !RESERVEDBITS) | PCONP_PCAD;
> > Where RESERVEDBITS is the bit mask of all of the reserved bits
> > for the PCONP register.
> >
> > Mike
> > I second that.
>
> But I think you meant to use ~ instead of ! as follows: :-)
> PCONP = (PCONP & ~RESERVEDBITS) | PCONP_PCAD;
>
> Alan
>

Alan,

You are of course correct. I must have had a brain fart.

Mike

--- In l..., "fitzdean" wrote:
>
> Is there some undocumented mystery on why the
> control register must first be initialized without
> the start conversion bit, and then re-written with
> the start conversion bit for it to work correctly?
>

There is a documented feature of the ADC of the first members of the
LPC2000 family of MCU's. You must not write the SEL and START
bitfields at the same time. The ADC of LPC213x is so called enhanced
ADC and there is not even a word for this feature in it's documentation.

> The last part of the sequence, where the start
> conversion bit is cleared shows up in 70% of the
> coding examples. Is that just a good habit, or
> another of those "lessons learned on the LPC2138"?
>

I think this is in connection with the above mentioned feature. START
bitfield is not a self cleared one and if you
forget to clear it, the next time when you
read/modify/write the ADxCR you run to a problem.


Memfault Beyond the Launch