NU32v2: Analog Input

From Mech
Jump to navigationJump to search


Our PIC32 has a single analog-to-digital converter (ADC) that, through the use of multiplexers, can be used to sample the analog voltage at up to 16 different pins (Port B). The ADC has 10-bit resolution, which means it can distinguish 2^10 = 1024 different voltage values, usually in the range 0 to 3.3 V, the voltage used to power the PIC32. This yields 3.3 V/1024 = 3 mV resolution. The ADC is typically used in conjunction with sensors that produce analog voltage values.


A block diagram of the ADC peripheral, taken from the Reference Manual, is shown below.


A single sample of an analog input consists of two steps: sample acquisition and sample conversion.

  • During the acquisition period, the selected input pin is connected to a sample and hold amplifier (SHA). This acquisition period must be of sufficient duration to allow the SHA output voltage to settle to the same voltage as the input pin. This takes finite time, considering the 4.4 pF internal holding capacitor.
  • Once the acquisition period has ended, the SHA is disconnected from the input pin. This allows the output of the SHA to be constant during the conversion process, even if the voltage on the input pin is changing. Conversion to a 10-bit digital value takes 12 ADC clock cycles: one for each of the 10 bits, plus two more.

The total time for a sample is the acquisition time plus the conversion time.

The conversion time can be understood by the fact that the ADC uses successive approximation to find the digital representation of the voltage. In this method, the SHA voltage is compared to the voltage produced by an internal digital-to-analog converter (DAC). The DAC takes a 10-bit number and produces a voltage proportional to the value (0x000 = minimum voltage, typically 0 V, and 0x3FF = maximum voltage, typically 3.3 V). In the first step of the conversion process, the DAC produces a voltage in the middle of the range, corresponding to the most significant bit = 1 and all others zero, i.e., 0x200 = 0b1000000000. If the SHA voltage is greater than the DAC voltage, the first bit of the conversion result is 1, otherwise it is zero. On the next cycle, the DAC's most significant bit is set to the result from the first test, and the second most significant bit is set to 1. The process continues until all 10 bits of the result are determined. This process is a binary search.

According to Table 31-37 of the Data Sheet (Electrical Characteristics section), the ADC clock period (, or Tad) must be at least 65 ns. Furthermore, the sampling time must be at least 132 ns. With this information, we find that the minimum sample time is (65 ns x 12) + 132 ns = 912 ns, indicating it is at least theoretically possible to take one million analog samples per second. However, we can only set the ADC clock period to be 2*k*(PBCLK period), where k is an integer from 1 to 256. For us the peripheral bus clock period is 1/(80 MHz) = 12.5 ns, so the smallest k we can choose is k=3, giving an ADC clock period of 2*3*12.5 ns = 75 ns. Thus our theoretical minimum sample time is (75 ns x 12) + 132 ns = 1.032 us.

When a conversion completes, the result is stored in a buffer of ADC results called ADC1BUF, which consists of 16 4-byte words. Your program can read from this buffer.


The ADC peripheral provides a plethora of options, some of which are described here:

  • Data format: The result of the conversion is stored in a 32-bit word, and it can be represented as a signed integer, integer, fractional value, etc. Typically we would use a16-bit or 32-bit integer.
  • Sampling and conversion initiation events: Sampling can be initiated by a software command, or immediately after the previous conversion has completed (auto sample). Conversion can be initiated by (1) a software command, (2) the expiration of a specified sampling period (auto convert), (3) a period match with Timer 3, or (4) a signal change on the INT0 pin. Typically only the first two are relevant to us. If sampling and conversion are being done automatically (not through software commands), the conversion results will be placed in the ADC1BUF in successively higher addresses, before returning to the first address in ADC1BUF after a specified number of conversions.
  • Input scan and alternating modes: You can read in a single analog input at a time, or you can scan through a list of analog inputs using MUX A in the figure above. Another mode is to alternate between two inputs, one from MUX A (channel A) and one from MUX B (channel B).
  • Voltage reference: The input range of the ADC is typically 0 to 3.3 V (the power rails of the PIC). If you are interested in voltages in a different range, say 1.0 V to 2.0 V, for example, you can set up the ADC so 0x000 corresponds to 1.0 V and 0x3FF corresponds to 2.0 V, to get better resolution in this smaller range: (2 V - 1 V)/1024 = 1 mV resolution. These reference voltage limits are VREF+ and VREF- and must be provided to the PIC externally. (These must be limited to the range 0 to 3.3 V; see Table 31-36 of the Data Sheet.)
  • Unipolar differential mode: Any of the analog inputs (say AN5) can be compared to AN1, so you read the difference between the voltage on AN5 and AN1 (where the voltage on AN5 is larger than the voltage on AN1).
  • Interrupts: An interrupt may be generated after a specified number of conversions.
  • ADC clock period: The ADC clock period Tad can range from 2 times the PB clock period up to 512 times the PB clock period, in integer multiples of two. You may also choose Tad to be the period of the ADC internal RC clock. (This appears to be the period of the internal fast RC oscillator (FRC) divided by 2, or (1/8MHz)/2 = 62.5 ns. See Chapter 22 of the Data Sheet. But this appears to conflict with the data sheet requirement that Tad be at least 65 ns. Should it be the FRC period multiplied by 2, to give 250 ns?)
  • Dual buffer mode for reading and writing conversion results: When an ADC conversion is complete, it is written into an output buffer ADC1BUF. After a series of one or more conversions is complete, an interrupt flag is set, indicating that the results are available for the program to read in. If the program is too slow to respond, however, the next set of conversions may begin to overwrite the previous results. To make this less likely, we can divide the 16-word ADC1BUF into two buffers, each consisting of 8 words: one in which the current conversions are being written, and one from which the program should read the previous results.

In the sample code below, we focus on just a few of the many possible configurations.


The operation of the ADC peripheral is determined by the following SFRs:

  • AD1PCFG: Only the least significant 16 bits are relevant. If a bit is 0, the associated pin is configured as an analog input. If a bit is 1, it is digital I/O.
  • AD1CON1: Determines whether ADC is on or off; the output format of the conversion; the "start conversion" signal generator; whether the ADC continually does conversions or just does one sequence of samples; and whether sampling begins upon again immediately after the previous conversion ends, or waits for a signal from the user. Also indicates if the most recent conversion is finished.
  • AD1CON2: Determines the voltage references for the ADC (positive reference could be 3.3 V or VREF+, negative reference could be GND or VREF-); whether or not inputs will be scanned; whether MUX A and MUX B will be used in alternating mode; whether dual buffer mode is selected; and the number of conversions to be done before generating an interrupt.
  • AD1CON3: Determines whether Tad is generated from the ADC internal RC clock or the PB clock; the number of Tad cycles to sample the signal; and the number of Tad cycles allowed for conversion of each bit (must be at least 65 ns).
  • AD1CHS: This SFR determines which pins will be sampled (the "positive" inputs) and what they will be compared to (i.e., VREF- or analog input 1). When in scan mode, the sample pins specified in this SFR are ignored.
  • AD1CSSL: This SFR indicates which analog inputs will be sampled in scan mode (if AD1CON2 has configured the ADC for scan mode). Inputs will be scanned from lower number inputs to higher numbers.

Apart from these SFRs, the ADC module has associated bits in the interrupt flag register IFS1, the interrupt enable control register IEC1, and the interrupt priority control register IPC6.

For more details, see the Reference Manual.

Library Functions

Library functions can be found in pic32-libs/include/peripheral/adc10.h.

  • SetChanADC10(config): Sets the AD1CHS SFR to config (i.e., which pins are sampled).
  • OpenADC10(config1, config2, config3, configport, configscan): The bits set as 1 in configport correspond to pins configured as analog inputs. The bits set as 0 in configscan are inputs that are included in a scan (AD1CSSL is the bitwise NOT of configscan). AD1CON3, AD1CON2, and AD1CON1 are set as config3, config2, and config1, respectively.
  • EnableADC10(): Sets the ON bit in AD1CON1 that indicates that the ADC is activated.
  • CloseADC10(): Turns off the ADC (clears the ON bit in ADC1CON1) and disables the interrupt.
  • ReadADC10(bufindex): Reads the conversion result stored in the bufindex'th element of the buffer ADC1BUF.
  • ReadActiveBufferADC10(): Returns 0 if conversions are currently being written into words 0-7 of ADC1BUF, and 1 if they are being written into words 8-15. Only relevant in the dual buffer mode.

Sample Code

Manual Conversion with SFRs

This sample code works with the SFRs directly. It simply does a conversion when requested in software. No automatic sampling or scanning of inputs. This code is modified from Example 17-1 in the Reference Manual. (untested)

int i,ADCValue;

AD1PCFG = 0xFFFB;     // PORTB = Digital; RB2 = analog in
AD1CON1 = 0x0000;     // SAMP bit = 0 ends sampling, starts converting
AD1CHS  = 0x00020000; // Connect RB2/AN2 as CH0 input
AD1CSSL = 0;          // no input scan, so don't specify inputs to scan
AD1CON3 = 0x0002;     // ADC clk period = 6 PB clk periods = 75 ns
AD1CON2 = 0;          // no input scan

// don't turn on the ADC until all other configuration is finished!
AD1CON1SET = 0x8000;  // turn ADC ON, defaults to software sampling

while (1) { 
  AD1CON1SET = 0x0002;  // set SAMP bit.  clears the DONE bit, starts sampling
  for (i=0; i<10; i++); // give it enough time to settle, 200 ns or more
  // a real timing operation would be better!  this would fail if optimized!
  AD1CON1CLR = 0x0002;  // conversion starts when SAMP bit is cleared
  while (!(AD1CON1 & 0x0001));  // check the DONE bit
  ADCValue = ADC1BUF0;  // when done, you can copy the value from ADC1BUF0

Auto-Convert Two Alternating Channels with Dual Buffer and the Peripheral Library

The following code uses the peripheral function library, and is modified from Microchip sample code in examples/plib_examples/adc10 to use the peripheral bus clock for Tad. (untested)

#include <plib.h>
#define SYS_FREQ 80000000

unsigned int channel4;	// conversion result as read from result buffer
unsigned int channel5;	// conversion result as read from result buffer
unsigned int offset;	// buffer offset to point to the base of the idle buffer

int main(void) {
  CloseADC10();	// ensure the ADC is off before setting the configuration

  // define setup parameters for OpenADC10
  // Turn module on | ouput in integer | trigger mode auto | enable autosample

  // define setup parameters for OpenADC10
  // ADC ref external | disable offset test | disable scan mode | perform 2 samples | use dual buffers | use alternating mode

  // define setup parameters for OpenADC10
  // use PB clock | Tad = 6 PB clk periods = 75 ns | use 10 Tad for sample time

  // define setup parameters for OpenADC10
  // set AN4 and AN5 as analog inputs

  // define setup parameters for OpenADC10
  // do not assign channels to scan
  // use ground as neg ref for A | use AN4 for input A | use ground as neg ref for A | use AN5 for input B
  // configure to sample AN4 & AN5
  OpenADC10(PARAM1, PARAM2, PARAM3, PARAM4, PARAM5); // configure ADC using the parameters defined above

  EnableADC10(); // Enable the ADC
  while (1) {
    while (!mAD1GetIntFlag());  // wait for conversion to complete
                                // instead of polling, could create an ISR with _ADC_VECTOR
    offset = 8 * ((~ReadActiveBufferADC10() & 0x01));  // determine which buffer is idle and create an offset
    channel4 = ReadADC10(offset);     // read result of channel 4 from idle buffer
    channel5 = ReadADC10(offset + 1); // read result of channel 5 from idle buffer
  return 0;

Auto-Convert One Channel with the Peripheral Library and Output to the Nokia 5110

The following code uses the peripheral function library, and code for the NU32v2: Nokia 5110 LCD. This sample code reads the analog voltage on AN0 (Port B, pin 0) and displays it to the Nokia LCD screen.

Full code is downloadable here.

Excerpts from the code: To initialize the ADC module:

  // configure and enable the ADC
  CloseADC10();	// ensure the ADC is off before setting the configuration

  // define setup parameters for OpenADC10
  // Turn module on | output in integer | trigger mode auto | enable  autosample

  // define setup parameters for OpenADC10
  // ADC ref external    | disable offset test    | enable scan mode | perform 1 sample | use one buffer | use MUXA mode

  // define setup parameters for OpenADC10
  // 				  use ADC internal clock | set sample time

  // define setup parameters for OpenADC10
  // set AN0 -> pin B0

  // define setup parameters for OpenADC10
  // assign channels you don't want to scan

  // use ground as neg ref for A | use AN0 (B0) for input A
  // configure to sample AN0
  SetChanADC10( ADC_CH0_NEG_SAMPLEA_NVREF); // configure to sample AN0
  OpenADC10( PARAM1, PARAM2, PARAM3, PARAM4, PARAM5 ); // configure ADC using parameter define above

  EnableADC10(); // Enable the ADC

To read the ADC value and write to the Nokia 5110, inside of the ISR for Timer 2, already set to 5Hz:

void __ISR(_TIMER_2_VECTOR, ipl2) Timer2Handler(void){
  char str[12]; // buffer for string written to Nokia 5110

  adcVal = ReadADC10(0); // read the adc (adcVal is unsigned short int)

  sprintf(str, "ADC = %4d", adcVal); // %4d provides 4 spaces for the integer

  float adcV;
  adcV = (float) adcVal * 3.3/1023.0;
  sprintf(str, "V = %5.3f", adcV); // %5.3f provides 5 spaces for the float with 3 after the decimal place

  // clear the interrupt flag

More Information