This time we will go through ARM Cortex-M3 LPC1343 ADC programming tutorial. We will see how to convert an Analog signal to its Digital version. We will also see LPC134x basic ADC Interfacing Example along with an interrupt based example.
LPC134x ADC Block
Analog to Digital Conversion is used when want to interface an external analog signal or when interfacing analog sensors, like for example a temperature sensor. The ADC block in ARM Cortex-M3 LPC1343 Microcontroller is based on Successive Approximation(SAR) conversion method. LPC1343 ADC Module uses 10-bit SAR having a conversion time of >= 2.44us i.e. around 409Khz max convertion rate. The Measurement range is from 0V to VDD, or commonly from 0V to ~3.3V (3.6V max). Maximum of 8 multiplexed inputs can be used for ADC.
Pins relating to ADC Module:
Pin | Description |
---|---|
AD0 to AD7 (PIO0_11, PIO1_0/1/2/3/4/10/11) | Analog input pins. Note from Datasheet: “IWhile the pins are 5 V tolerant in digital mode, the maximum input voltage must not exceed VDD when the pins are configured as analog inputs.” |
VDD (VREF) | VDD itself is used as reference voltage pins for ADC block. |
VDD, VSS | VDD is Analog Power pin and VSS is Ground pin used to power the ADC module. |
LPC134x ADC Registers
Lets see registers which are used for ADC programming.
- Bits[7:0] – SEL: Bit ‘x'(in this group) is used to select pin ADx.
- Bits[15:8] – CLKDIV: ADC Peripheral clock i.e. PCLK(same as PCLK for lpc134x) is divided by CLKDIV+1 to get the ADC clock. Note that ADC clock speed must be <= 4.5Mhz! As per user manual user must program the smallest value in this field which yields a clock speed of around 4.5 MHz or a bit less.
- Bit[16] – BURST: Set to 1 for doing repeated conversions, else 0 for software controlled conversions. Note: START bits must be set to 000 when BURST=1 or conversions will not start. Refer user manual for detailed info.
- Bits[19 to 17] – CLKS : These bits are used to select the number of clocks used for conversion in burst mode along with number of bits of accuracy of the result in RESULT bits of ADDR.
Value clocks / bits 0x0 11 clocks / 10 bits 0x1 10 clock / 9 bits 0x2 9 clock / 8 bits 0x3 8 clock / 7 bits 0x4 7 clock / 6 bits 0x5 6 clock / 5 bits 0x6 5 clock / 4 bits 0x7 4 clock / 3 bits - Bits[26:24] – START: These bits are used to control the start of ADC conversion when BURST (bit 16) is set to 0. 0x0 = No start , 0x1 = Start the conversion now, and rest(0x2 to 0x7) of the values are used to start conversion when edge selected by bit 27(given below) occurs on a corresponding pin – refer LPC13xx User Manual for more.
- Bit[27] – EDGE: Set this bit to 1 to start the conversion on falling edge of the selected CAP/MAT signal and set this bit to 0 to start the conversion on rising edge of the selected signal. (Note: This bit is of used only in the case when the START contains a value between 0x3 to 0x7 as mentioned above.)
- Other bits are reserved.
2) ADGDR – A/D Global Data Register : Contains the ADC’s flags and the result of the most recent A/D conversion.
- Bits[15:6] – V_VREF (RESULT): When DONE bit = 1, these bits give a binary fraction which represents the voltage on the pin ADx, divided by voltage in VDD. Value of 0x0 indicates that voltage on the given pin was less than, equal to or close to VSS(i.e. GND). And a value of 0x3FF means that the input voltage was close to, equal to or greater than the VDD.
- Bits[26:24] – CHN: Represents the channel from which V_VREF bits were converted. 0x0 = channel 0, 0x1= channel 1 and so on.
- Bit[30] – OVERRUN: In burst mode this bit is 1 in case of an Overrun i.e. the result of previous conversion being lost(overwritten).
- Bit[31] – DONE: When ADC conversion completes this bit is 1. When this register(ADGDR) is read and ADCR is written, this bit gets cleared i.e. set to 0. If ADCR is written while a conversion is in progress then this bit is set and a new conversion is started.
- Other bits are reserved.
4) ADDR0 to ADDR7 – A/D Data registers : This register contains the result of the most recent conversion completed on the corresponding channel [0 to 7]. Its structure is same as ADGDR except Bits[26:24] are not used/reserved.
5) ADSTAT – A/D Status register : This register contains DONE and OVERRUN flags for all of the A/D channels along with A/D interrupt flag.
- Bits[7:0] – DONE[7 to 0]: Here xth bit mirrors DONEx status flag from the result register for A/D channel x.
- Bits[15:8] – OVERRUN[7 to 0]: Even here the xth bit mirrors OVERRUNx status flag from the result register for A/D channel x
- Bit 16 – ADINT: This bit represents the A/D interrupt flag. It is 1 when any of the individual A/D channel DONE flags is asserted and enabled to contribute to the A/D interrupt via the ADINTEN(given below) register.
- Other bits are reserved.
6) ADINTEN – A/D Interrupt Enable Register : Using this register interrupts can be enabled or disabled for any ADC channels.
- Bits[0 to 7] – ADINTEN[7:0]: If xth bit is set to 1 then an interrupt will be generated on completion of A/D conversion on channel x. Similarly if this bit is set to 0 then an interrupt wont be generated.
- Bit 8 – ADGINTEN: When set to 1, the global DONE flag in AADR will be used to generate an interrupt. When 0, only individual channels enabled by ADINTEN[7:0] will generate interrupts
ADC Modes, Setup and Programming
ADC modes in LPC1343:
- Software controlled mode : In Software mode only one conversion will be done at a time. To perform another conversion you will need to restart the process. In software mode, only 1 bit in the SEL field of ADCR can be 1 i.e. only 1 Channel(i.e. Pin) can be selected for conversion at a time. Hence conversions can be done only any channel but one at a time.
- Hardware or Burst mode : In Hardware or Burst mode, conversions are performed continuously on the selected channels in round-robin fashion. Since the conversions cannot be controlled by software, Overrun may occur in this mode. Overrun is the case when a previous conversion result is replaced by new conversion result without previous result being read i.e. the conversion is lost. Usually an interrupt is used in Burst mode to get the latest conversion results. This interrupt is triggered when conversion in one of the selected channel ends.
1A. Setting up and configuring ADC Module for software controlled mode
First, lets define some values which will help us in setting up ADCR register to configure & Initialize ADC block.
#define ADC_CLK_EN (1<<13) //Enable ADC clock
#define SEL_AD0 (1<<0) //Select Channel AD0
#define CLKDIV 15 // ADC clock-divider (ADC_CLOCK=PCLK/CLKDIV+1), yields 4.5Mhz ADC clock
#define ADC_PWRUP (~(1<<4)) //setting it to 0 will power it up
#define START_CNV (1<<24) //001 for starting the conversion immediately
#define ADC_DONE (1U<<31) //define it as unsigned value or compiler will throw #61-D warning
#define ADCR_SETUP_SCM (CLKDIV<<8)
//SCM = Software Controlled Mode
Now we assign ADCR_SETUP to ADCR along with channel selection information to select channels as required. Finally we assign(by ORing) START_NOW to ADCR to start the conversion process as shown:
LPC_SYSCON->PDRUNCFG &= ADC_PWRUP; //Power-up ADC Block
LPC_SYSCON->SYSAHBCLKCTRL |= ADC_CLK_EN; //Enable ADC clock
LPC_ADC->CR = ADCR_SETUP_SCM | SEL_AD0; //Setup ADC Block
LPC_ADC->CR |= START_CNV; //Start Conversion immediately
Note that before setting ADC using ADCR the ADC block must be powered-up using PDRUNCFG register and ADC Clock must be enabled using SYSAHBCLKCTRL register.
1B. Fetching the conversion result in software controlled mode :
In software controlled mode we continuously monitor bit 31 in the corresponding channel data register ADDR. If bit 31 changes to 1 from 0, it means that current conversion has been completed and the result is ready. For example, if we are using channel 0 of AD0 then we monitor for changes in bit 31 as follows :
while((LPC_ADC->DR0 & ADC_DONE) == 0); //this loop will end when bit 31 of AD0DR6 changes to 1.
After this we extract the result which is stored in ADDR bits 4 to 15. Here we right shift ADDR by 6 places and force all other unrelated bits to 0 by using a 10-bit mask value of 0x3FF. 0x3FF is a mask containing 1's in bit locations 0 to 9 and rest 0's. In our case with ADDR0 being used, it can be done as follows :
result = (LPC_ADC->DR0>>6) & 0x3FF;
2A. Setting up and configuring ADC Module for Burst mode
Configuring ADC Module is similar to what was done in software controlled mode except here we use the CLKS bits and don't use the START bits in ADCR. ADC_DONE is also not applicable since we are using an ISR which gets triggered when a conversion completes on any of the enabled channels. Additionally, we define the following constants:
#define BURST_ON (1<<16) // 1 for on and 0 for off
#define ADCR_SETUP_BURST ((CLKDIV<<8) | BURST_ON)
We configure and setup the ADC module in a similar manner(as shown above) as follows :
LPC_SYSCON->PDRUNCFG &= ADC_PWRUP; //Power-up ADC Block
LPC_SYSCON->SYSAHBCLKCTRL |= ADC_CLK_EN; //Enable ADC clock
LPC_ADC->CR = ADCR_SETUP_BURST | SEL_AD0; //Setup ADC Block
Note that in this case we can select multiple channels for conversion when setting up AD0CR. START bits are not applicable here since the conversions starts as soon we setup AD0CR register.
2B. Fetching the conversion result in Burst mode :
In Burst mode we use an ISR which triggers at the completion of a conversion in any one of the channel. Now, we just need to find the Channel for which the conversion was done. For this we fetch the channel number from ADGDR which also stores the conversion result. Bits [26:24] in ADGDR contain the channel number. Hence, we shift it 24 places and use a 3-bit mask value of 0x7 as shown below:
unsigned long AD_GDR_Read = LPC_ADC->GDR;
int channel = (AD_GDR_Read>>24) & 0x7; //Extract Channel Number
After knowing the Channel number, we have 2 options to fetch the conversion result from. Either we can fetch it from ADGDR or from ADDRx of the corresponding channel. Lets use ADGDR for extracting the conversion result as follows:
int currentResult = (AD_GDR_Read>>6) & 0x3FF; //Extract Conversion Result
Selecting AD Alternate function using IOCON (LPC_IOCON)
To select AD0 function for pin PIO0_11 we just need to set FUNC bits[2:0] to 0x2 and set the ADMODE bit[7] to 0. In latest(current) versions of CMSIS library the member R_PIOO_11 of LPC_IOCON is used as LPC_IOCON->R_PIO0_11 = 0x2; but in older versions of CMSIS (used by CoIDE[as of now]) R_PIO0_11 was defined as JTAG_TDI_PIO0_11. Hence in this case you will have to use LPC_IOCON->JTAG_TDI_PIO0_11 = 0x2;. Newer versions of CMSIS included in LPCXpresso and MCUXpresso support both of them but KEIL doen't support LPC_IOCON->JTAG_TDI_PIO0_11. So just keep this in mind while programming in KEIL or CoIDE.
ARM Cortex-M3 LPC1343 ADC Example
Note that in this case no input protection nor filtering was required. But, when Interfacing external analog signals it is recommended to use some form of input protection.
Interfacing Potentiometer using ADC on LPC134x
This example performs Analog to Digital conversion in Software Controlled Mode. Here we use PIO0_11(P0.11) as analog input for measuring the voltage. PIO0_11 corresponds to Channel 0 of ADC i.e. AD0. For testing I had used a 10K potentiometer for ADC Interfacing and connected the middle leg of POT to PIO0_11 of my LPC1343 development board. You can also use a 5K potentiometer for this ADC Example. PIO0_11 is marked as AD0 on LPCXpresso board. Schematic for this example is as shown below:
Source Code :
/*(C) Umang Gajera - www.ocfreaks.com
LPC1343 ADC Interfacing Example 1 Source Code for MCUXpresso
More Embedded tutorials @ www.ocfreaks.com/cat/embedded/
License : GPL.*/
#include <lpc17xx.h>
#include <stdio.h>
#include "ocf_LPC134x_lib.h" //contains code for UART, Timer & to retarget printf().
#define VREF 3.3 //Reference Voltage at VREFP pin, given VREFN = 0V(GND)
#define ADC_CLK_EN (1<<13) //Enable ADC clock
#define SEL_AD0 (1<<0) //Select Channel AD0
#define CLKDIV 15 // ADC clock-divider (ADC_CLOCK=PCLK/CLKDIV+1), yields 4.5Mhz ADC clock
#define ADC_PWRUP (~(1<<4)) //setting it to 0 will power it up
#define START_CNV (1<<24) //001 for starting the conversion immediately
#define ADC_DONE (1U<<31) //define it as unsigned value or compiler will throw warning
#define ADCR_SETUP_SCM (CLKDIV<<8)
int main(void)
{
//SystemInit(); //Gets called by Startup code, sets CCLK=100Mhz, PCLK=25Mhz
initUART(); //Initialize UART for printf()
initTimer0(); //For delayMS()
LPC_SYSCON->PDRUNCFG &= ADC_PWRUP; //Power-up ADC Block
LPC_SYSCON->SYSAHBCLKCTRL |= ADC_CLK_EN; //Enable ADC clock
LPC_ADC->CR = ADCR_SETUP_SCM | SEL_AD0; //Setup ADC Block
/* Now select AD0 function and set ADMODE=0 for PIO0_11(P0.11) */
LPC_IOCON->R_PIO0_11 = 0x2; //Use this for KEIL and LPCXpresso/MCUXpresso - check tutorial for more
//LPC_IOCON->JTAG_TDI_PIO0_11 = 0x2; //Older version of CMSIS uses this. Uncomment this for CoIDE.
unsigned int result = 0;
float voltage = 0;
printf("OCFreaks.com LPC134x ADC Tutorial Example 1.\nSoftware Controlled ADC Mode on AD0 Channel.\n");
while(1)
{
LPC_ADC->CR |= START_CNV; //Start new Conversion
while((LPC_ADC->DR0 & ADC_DONE) == 0); //Wait until conversion is finished
result = (LPC_ADC->DR0>>6) & 0x3FF; //10 bit Mask to extract result
voltage = ((float)result * VREF) / 1024;
printf("AD0 = %dmV\n" , (int)(voltage*1000)); //Print millivolts
delayMS(500); //Slowing down Updates to 2 Updates per second
}
//return 0; //This won't execute
}
MCUXpresso Project for Example given above is on GitHub @ LPC134x ADC Interfacing Example 1 [Successfully tested on MCUXpresso v10.0.2], Download Project Workspace Zip
CoIDE Project on GitHub @ LPC134x ADC Interfacing Example 1 [Successfully tested on CooCox CoIDE v1.7.8], Download Project Zip
LPC1343 ADC Interrupt Example
Now, lets cover an example using Hardware controlled mode or Burst mode using ADC Interrupt. In this mode we will have to use ADC interrupt since the ADC block will scan the selected channels and generated interrupt when a conversion gets finished on a particular channel. It scans the channels in round-robin fashion.
In this example too, we will use PIO0_11(P0.11) for measuring the voltage. The connections are same as given in example 1. The ADC ISR ADC_IRQHandler() gets called everytime conversion is finished on selected channel.
Source Code:
/*(C) Umang Gajera - www.ocfreaks.com
LPC1343 ADC Interrupt Example Source Code for MCUXpresso
More Embedded tutorials @ www.ocfreaks.com/cat/embedded/
License : GPL.*/
#include <lpc17xx.h>
#include <stdio.h>
#include "ocf_LPC134x_lib.h" //contains code for UART, Timer & to retarget printf().
#define VREF 3.3 //Reference Voltage at VREFP pin, given VREFN = 0V(GND)
#define ADC_CLK_EN (1<<13) //Enable ADC clock
#define SEL_AD0 (1<<0) //Select Channel AD0
#define CLKDIV 15 // ADC clock-divider (ADC_CLOCK=PCLK/CLKDIV+1), yields 4.5Mhz ADC clock
#define ADC_PWRUP (~(1<<4)) //setting it to 0 will power it up
#define START_CNV (1<<24) //001 for starting the conversion immediately
#define ADC_DONE (1U<<31) //define it as unsigned value or compiler will throw warning
#define ADC_BURST (1<<16) //Enable burst(hardware) mode
#define ADCR_SETUP_BURST ((CLKDIV<<8) | ADC_BURST)
int AD0Result = 0;
int main(void)
{
//SystemInit(); //Gets called by Startup code, sets CCLK=100Mhz, PCLK=25Mhz
initUART(); //Initialize UART for printf()
initTimer0(); //For delayMS()
LPC_SYSCON->PDRUNCFG &= ADC_PWRUP; //Power-up ADC Block
LPC_SYSCON->SYSAHBCLKCTRL |= ADC_CLK_EN; //Enable ADC clock
LPC_ADC->CR = ADCR_SETUP_BURST | SEL_AD0; //Setup ADC Block
/* Now select AD0 function and set ADMODE=0 for PIO0_11(P0.11) */
LPC_IOCON->R_PIO0_11 = 0x2; //Use this for KEIL and LPCXpresso/MCUXpresso - check tutorial for more
//LPC_IOCON->JTAG_TDI_PIO0_11 = 0x2; //Older version of CMSIS uses this. Uncomment this for CoIDE.
LPC_ADC->INTEN = SEL_AD0; //Enable AD0 Interrupt, set ADGINTEN to 0
NVIC_EnableIRQ(ADC_IRQn); //Enable ADC IRQ
float voltage = 0;
printf("OCFreaks.com LPC134x ADC Interrupt Tutorial Example.\n");
printf("BURST/Hardware-Controlled ADC Mode on AD0 Channel.\n");
while(1)
{
voltage = ((float)AD0Result * VREF) / 1024;
printf("AD0 = %dmV\n" , (int)(voltage*1000)); //Print millivolts
delayMS(500); //Slowing down Updates to 2 Updates per second
}
//return 0; //This won't execute
}
void ADC_IRQHandler(void)
{
unsigned long ADC_GDR_Read = LPC_ADC->GDR;
int channel = (ADC_GDR_Read>>24) & 0x7; //Extract Channel Number
int currentResult = (ADC_GDR_Read>>6) & 0x3FF; //Extract Conversion Result
if(channel == 0)
{
AD0Result = LPC_ADC->DR0; //Dummy read to Clear Done bit
AD0Result = currentResult;
}
}
MCUXpresso Project for Example given above is on GitHub @ LPC134x ADC Interrupt Example [Successfully tested on MCUXpresso v10.0.2], Download Project Workspace Zip
CoIDE Project on GitHub @ LPC134x ADC Interrupt Example [Successfully tested on CooCox CoIDE v1.7.8], Download Project Zip
Discover more from OCFreaks!
Subscribe to get the latest posts sent to your email.