LPC11C24 converting ADC value to voltage

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

LPC11C24 converting ADC value to voltage

1,737 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by One33Seven on Sat Oct 04 01:33:21 MST 2014
I'm using the examples from the CMSIS package as a driver for the onchip ADC.

In my main function I call ADCInit( ADC_CLK ); to initialize the ADC and later on in a timer I use ADCValue = ADCRead(0); to read the ADC.

Every works fine except the converted value doesn't match the voltage on the pin. The voltage on the PIN is 2.5v and the value the ADC is giving me is 590 (uint).

So this is how I'm calculating to real value, the range of the ADC is 3.3v and it's in 10bit mode so 1024. 3.3/1024 = 0.0032v LSB.

590*0.0032 = 1.9v which is not 2.5v.

What am I doing wrong?


#ifndef __ADC_H
#define __ADC_H

#define ADC_INTERRUPT_FLAG    0/* 1 is interrupt driven, 0 is polling */
#define BURST_MODE            0 /* Burst mode works in interrupt driven mode only. */
#define SINGLE_BURST_MODE     0 /* Single burst mode works in interrupt driven mode only. */
#define ADGINTEN              0/* global DONE flag generate interrupt */
#define INTERNAL_VOLT         1/* If this flag is on, it measures interval voltage,
                                   core voltage, internal Vref, and temperature. */
#define ADC_DEBUG             0
#define ADC_DEBUG_CLEAR_CNT   (100) /* Number of samples until statistics are cleared */
#define ADC_DEBUG_UPDATE_CNT  (5000) /* Number of samples until statistics are cleared */

#define ADC_OFFSET            0x10
#define ADC_INDEX             4

#define ADC_DONE              0x80000000
#define ADC_OVERRUN           0x40000000
#define ADC_ADINT             0x00010000

#define ADC_NUM               8/* for LPC11xx */
#define ADC_CLK               4400000/* set to 4.4Mhz */


extern void ADC_IRQHandler( void );
extern void ADCInit( uint32_t ADC_Clk );
extern uint32_t ADCRead( uint8_t channelNum );
extern void ADCBurstRead( void );
#endif /* end __ADC_H */
/*****************************************************************************
**                            End Of File
******************************************************************************/


#include "LPC11xx.h"/* LPC11xx Peripheral Registers */
#include "adc.h"

volatile uint32_t ADCValue[ADC_NUM];
volatile uint32_t ADCIntDone = 0;
volatile uint32_t OverRunCounter = 0;

#if BURST_MODE
volatile uint32_t channel_flag = 0;
#endif

#if ADC_INTERRUPT_FLAG
/******************************************************************************
** Function name:ADC_IRQHandler
**
** Descriptions:ADC interrupt handler
**
** parameters:None
** Returned value:None
**
******************************************************************************/
void ADC_IRQHandler (void)
{
  uint32_t regVal, i;

  regVal = LPC_ADC->STAT;/* Read ADC will clear the interrupt */
  if ( regVal & 0x0000FF00 )/* check OVERRUN error first */
  {
OverRunCounter++;
for ( i = 0; i < ADC_NUM; i++ )
{
  regVal = (regVal & 0x0000FF00) >> 0x08;
  /* if overrun, just read ADDR to clear */
  /* regVal variable has been reused. */
  if ( regVal & (0x1 << i) )
  {
regVal = LPC_ADC->DR;
  }
}
LPC_ADC->CR &= 0xF8FFFFFF;/* stop ADC now */
ADCIntDone = 1;
return;
  }

  if ( regVal & ADC_ADINT )
  {
for ( i = 0; i < ADC_NUM; i++ )
{
  if ( (regVal&0xFF) & (0x1 << i) )
  {
ADCValue = ( LPC_ADC->DR >> 6 ) & 0x3FF;
  }
}

#if BURST_MODE
channel_flag |= (regVal & 0xFF);
if ( (channel_flag & 0xFF) == 0xFF )
{
  /* All the bits in have been set, it indicates all the ADC
  channels have been converted. */
  LPC_ADC->CR &= 0xF8FFFFFF;/* stop ADC now */
  channel_flag = 0;
  ADCIntDone = 1;
}
#else
LPC_ADC->CR &= 0xF8FFFFFF;/* stop ADC now */
ADCIntDone = 1;
#endif
  }
  return;
}
#endif

/*****************************************************************************
** Function name:ADCInit
**
** Descriptions:initialize ADC channel
**
** parameters:ADC clock rate
** Returned value:None
**
*****************************************************************************/
void ADCInit( uint32_t ADC_Clk )
{
  uint32_t i;

  /* Disable Power down bit to the ADC block. */
  LPC_SYSCON->PDRUNCFG &= ~(0x1<<4);

  /* Enable AHB clock to the ADC. */
  LPC_SYSCON->SYSAHBCLKCTRL |= (1<<13);

  for ( i = 0; i < ADC_NUM; i++ )
  {
ADCValue = 0x0;
  }
  /* Unlike some other pings, for ADC test, all the pins need
  to set to analog mode. Bit 7 needs to be cleared according
  to design team. */
  LPC_IOCON->R_PIO0_11 &= ~0x8F; /*  ADC I/O config */
  LPC_IOCON->R_PIO0_11 |= 0x02;  /* ADC IN0 */
  //LPC_IOCON->R_PIO1_0  &= ~0x8F;
  //LPC_IOCON->R_PIO1_0  |= 0x02;  /* ADC IN1 */
  //LPC_IOCON->R_PIO1_1  &= ~0x8F;
  //LPC_IOCON->R_PIO1_1  |= 0x02;  /* ADC IN2 */
  //LPC_IOCON->R_PIO1_2 &= ~0x8F;
  //LPC_IOCON->R_PIO1_2 |= 0x02; /* ADC IN3 */

/* AD4 disabled to retain SWD. Un-comment if needed */
// LPC_IOCON->SWDIO_PIO1_3   &= ~0x8F;
  // LPC_IOCON->SWDIO_PIO1_3   |= 0x02;  /* ADC IN4 */
  /*--------------------------------------------*/

//LPC_IOCON->PIO1_4    &= ~0x8F; /* Clear bit7, change to analog mode. */
  //LPC_IOCON->PIO1_4    |= 0x01;  /* ADC IN5 */
  //LPC_IOCON->PIO1_10   &= ~0x8F; /* Clear bit7, change to analog mode. */
  //LPC_IOCON->PIO1_10   |= 0x01;  /* ADC IN6 */
  //LPC_IOCON->PIO1_11   &= ~0x8F; /* Clear bit7, change to analog mode. */
  //LPC_IOCON->PIO1_11   |= 0x01;  /* ADC IN7 */

  LPC_ADC->CR = ( 0x01 << 0 ) |  /* select AD0 on PIO0_11. SEL=1,select channel 0~7 on ADC0 */
( ( SystemCoreClock / ADC_Clk - 1 ) << 8 ) |  /* CLKDIV = Fpclk / 1000000 - 1 */
( 0 << 16 ) | /* BURST = 0, no BURST, software controlled */
( 0 << 17 ) |  /* CLKS = 0, 11 clocks/10 bits */
( 1 << 21 ) |  /* PDN = 1, normal operation */
( 0 << 22 ) |  /* TEST1:0 = 00 */
( 0 << 24 ) |  /* START = 0 A/D conversion stops */
( 0 << 27 );/* EDGE = 0 (CAP/MAT singal falling,trigger A/D conversion) */

  /* If POLLING, no need to do the following */
#if ADC_INTERRUPT_FLAG
  NVIC_EnableIRQ(ADC_IRQn);
#if BURST_MODE
  LPC_ADC->INTEN = 0xFF;/* Enable all interrupts */
#else
  LPC_ADC->INTEN = 0x1FF;/* Enable all interrupts */
#endif
#endif
  return;
}

/*****************************************************************************
** Function name:ADCRead
**
** Descriptions:Read ADC channel
**
** parameters:Channel number
** Returned value:Value read, if interrupt driven, return channel #
**
*****************************************************************************/
uint32_t ADCRead( uint8_t channelNum )
{
#if !ADC_INTERRUPT_FLAG
  uint32_t regVal, ADC_Data;
#endif

  /* channel number is 0 through 7 */
  if ( channelNum >= ADC_NUM )
  {
channelNum = 0;/* reset channel number to 0 */
  }
  LPC_ADC->CR &= 0xFFFFFF00;
  LPC_ADC->CR |= (1 << 24) | (1 << channelNum);
/* switch channel,start A/D convert */
#if !ADC_INTERRUPT_FLAG
  while ( 1 )/* wait until end of A/D convert */
  {
regVal = *(volatile unsigned long *)(LPC_ADC_BASE
+ ADC_OFFSET + ADC_INDEX * channelNum);
/* read result of A/D conversion */
if ( regVal & ADC_DONE )
{
  break;
}
  }

  LPC_ADC->CR &= 0xF8FFFFFF;/* stop ADC now */
  if ( regVal & ADC_OVERRUN )/* save data when it's not overrun, otherwise, return zero */
  {
return ( 0 );
  }
  ADC_Data = ( regVal >> 6 ) & 0x3FF;
  return ( ADC_Data );/* return A/D conversion value */
#else
  return ( channelNum );/* if it's interrupt driven, the ADC reading is
done inside the handler. so, return channel number */
#endif
}

/*****************************************************************************
** Function name:ADC0BurstRead
**
** Descriptions:Use burst mode to convert multiple channels once.
**
** parameters:None
** Returned value:None
**
*****************************************************************************/
void ADCBurstRead( void )
{
  if ( LPC_ADC->CR & (0x7<<24) )
  {
LPC_ADC->CR &= ~(0x7<<24);
  }
  /* Read all channels, 0 through 7. Be careful that if the ADCx pins is shared
  with SWD CLK or SWD IO. */
  LPC_ADC->CR |= (0xFF);
  LPC_ADC->CR |= (0x1<<16);/* Set burst mode and start A/D convert */
  return;/* the ADC reading is done inside the
handler, return 0. */
}

/*********************************************************************************
**                            End Of File
*********************************************************************************/
Labels (1)
0 Kudos
8 Replies

1,358 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by IanB on Wed Oct 08 05:35:06 MST 2014
It happens to us all.  . .

However, perhaps stlll a good idea to delete this:

( 1 << 21 ) | /* PDN = 1, normal operation */


and change this:

( ( SystemCoreClock / ADC_Clk - 1 ) << 8 ) |


to

( ( SystemCoreClock / ADC_Clk) << 8 ) |


because the manual says not to set bit 21 to 1, and you are running it too fast.
0 Kudos

1,358 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by One33Seven on Wed Oct 08 05:09:53 MST 2014
Sometimes it's the simplest things that you overlook. During final design verification someone somehow the ADC was changed from 0 to 1 and we had discussed to use ADC0. THe only thing I had todo was use ADC1 instead of 0.
0 Kudos

1,358 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by IanB on Mon Oct 06 12:08:29 MST 2014
This line is suspect:
( 1 << 21 ) |  /* PDN = 1, normal operation */

The manual says not to write ones to bits 20-23

This line:
( ( SystemCoreClock / ADC_Clk - 1 ) << 8 ) |

is giving the wrong answer, because it should round up not down, SystemCoreClock / ADC_Clk without the -1 will give the right value.

and this line
 LPC_ADC->CR &= 0xFFFFFF00;

isn't helping because it unnecessarily changes the multiplexer back to channel AD0, whereas it would be far better not to disturb it unless you need to change channels.

Just wondering . . . and I hope I'm not stating the obvious here . . . Are you sure you're reading the correct A/D? You're reading AD0, but, according to your circuit diagram, the +12V divider is connected to AD1 (which is PIO1_0)

If you want to read AD1 then shouldn't it be
 ADCValue = ADCRead(1);
0 Kudos

1,358 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by IanB on Mon Oct 06 06:41:30 MST 2014
All sorts of interesting things going on here:

The control register contains 0x200901 - Only AD0 is selected for conversion, and from you diagram your input is on AD1.
The CLKDIV is set to 9, so it divides by 10. Assuming a 48MHz clock, that's too fast (as the clock should be 4.4MHz or slower)
Then there's a 1 in bit 21 - bits 20-23 are labelled "do not write ones to these bits".

Then you've got bit 30 set in the GDR, indicating an overrun (data has not been read before the next conversion overwrote it), but you haven't got bit 31 set, which would be set when a conversion completes (but that's probably not important as it gets reset as soon as GDR is read)

The result (which belongs to AD0) is 0x8900, which is 53% of VDD (1.75V).

My routine is written assembler (you can have the code if you want), but it does this:

Write 0x0F02 to the AD0CR [control register] (selects divide by 16, input AD1)
Wait for the input to settle (3 x NOP should do, longer if you can spare the time)
OR AD0CR with 1<<24 (sets bit 24 to start the conversion)
Wait until bit 31 is set in AD0GDR (just test for negative)
Read the data either from AD0GDR or from AD0DR1
AND with 0xFFFF


0 Kudos

1,358 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by One33Seven on Mon Oct 06 05:31:35 MST 2014
I have several of these boards already produced as a prototype and all of them measure a different voltage. ale of them have 2.5V on them but the measured value over 5 boards ranges from 1.7 to 3 volt.

I was thinking about an offset but the measured value isn't linear. when I vary between 1 and 3 volt on the bus the measured value doesn't scale along.

I'm not sure what's going on, I'm thinking about some weird interaction between the input and voltagedivider but what?

This is the periphals tab:
[img]https://dl.dropboxusercontent.com/u/13819180/Untitled4.png[/img]
0 Kudos

1,358 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by IanB on Sun Oct 05 09:54:47 MST 2014
Are you using any of the other A/D inputs? If you're only using AD1 then this isn't the reason.

Input impedance is not the question - you need to think of it in the time domain.

AFTER the channel multiplexer changes, it takes a while before the input of the A/D is at the same voltage as the pin.
It takes about 7 time constants to get it to within 1 bit. The C in the time constant is the 1pF of AD input capacitance. The R is your source impedance (about 5kΩ) PLUS the resistance of the multiplexer (3.3kΩ). So 1 time constant is 8.3kΩ x 1pF = 8.3ns. So you need 58ns for it to settle - 3 instructions at full speed - before conversion can start.

If it has just converted a different input, which is at 0V, then it will read low.

However - if you are only using AD1, it's something else - if you look at "Peripherals" and select the A/D, what values are shown in the registers?
0 Kudos

1,358 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by One33Seven on Sun Oct 05 07:27:45 MST 2014
The ADC is used to measure a 12V input to guard against line voltage drops or over voltage.

I use a voltage-divider to scale the 12v down to a measurable range (doesn't have to be too accurate as long as it's in the 12v range).

[img]https://dl.dropboxusercontent.com/u/13819180/Untitled3.png[/img]

According to the datasheet the ADC has an input impedance of 2.5M so this shouldn't be a problem.
0 Kudos

1,358 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by IanB on Sun Oct 05 05:16:18 MST 2014
What is the source impedance of your analogue signal?
Your function appears to set the multiplexer and start the conversion in one instruction.
If the multiplexer has changed, this won't allow enough time to charge the input capacitance of the A/D.

Have you tried other input voltages?

I was thinking about this last week, and answered my own question when I eventually found the data in the data sheet.

http://www.lpcware.com/content/forum/adc-settling-time

0 Kudos