Analog input on the dsPIC30F4011

The following example program demonstrates basic analog input on the dsPIC30F4011. Only one of the ADC module’s four converters is used (AD0) and only one channel at a time is read. The sampling time is controlled manually and is currently set to 1µs, which should be adequate for any source impedance below the recommended maximum value of 5kΩ. The total time per measurement (including the sampling and conversion) should be less than 2.5µs.

I haven’t tested this thoroughly yet, but I have successfully used it to control the duty cycle of an LED (i.e. its brightness) with a pot connected to AN0 (pin 2).

// This is a simple analog input example for the dsPIC30F4011
// It configures the lowest 8 PORTB pins as analog inputs, then reads
// the voltage on AN0 repeatedly. The input voltage determines
// the brightness of an LED on RD0 by varying its duty cycle
// in proportion to the analog input reading.
// Written by Ted Burke - last updated 5-12-2010

#include <libpic30.h>
#include <p30f4011.h>

_FOSC(CSW_FSCM_OFF &amp; FRC_PLL16); // Clock speed = 7.5MHz x 16, i.e. 30 MIPS
_FWDT(WDT_OFF); // Watchdog timer off

void configure_pins();
unsigned int read_analog_channel(int n);

int main()
	long int n;

	// Set up which pins are which

		// Read the analog channel. The result is an
		// integer between 0 and 1023 inclusive.
		n = read_analog_channel(0);

		// LED on
		_LATD0 = 1;
		__delay32(30*n); // m us

		// LED off
		_LATD0 = 0;
		__delay32(30*(1023 - n)); // (1023-n) us

	return 0;

void configure_pins()
	// Configure digital I/O
	LATD = 0;
	TRISD = 0b11111110;

	// Configure analog inputs
	TRISB = 0x01FF;      // Port B all inputs
	ADPCFG = 0xFF00;     // Lowest 8 PORTB pins are analog inputs
	ADCON1 = 0;          // Manually clear SAMP to end sampling, start conversion
	ADCON2 = 0;          // Voltage reference from AVDD and AVSS
	ADCON3 = 0x0005;     // Manual Sample, ADCS=5 -&gt; Tad = 3*Tcy = 0.1us
	ADCON1bits.ADON = 1; // Turn ADC ON

// This function reads a single sample from the specified
// analog input. It should take less than 2.5us if the chip
// is running at about 30 MIPS.
unsigned int read_analog_channel(int channel)
	ADCHS = channel;          // Select the requested channel
	ADCON1bits.SAMP = 1;      // start sampling
	__delay32(30);            // 1us delay @ 30 MIPS
	ADCON1bits.SAMP = 0;      // start Converting
	while (!ADCON1bits.DONE); // Should take 12 * Tad = 1.2us
	return ADCBUF0;
This entry was posted in PIC and tagged , , , , , , . Bookmark the permalink.

11 Responses to Analog input on the dsPIC30F4011

  1. I love THAT post BUT i wanted to leave a comment on the previous post in which you gave the blinking LED code. On my dsPIC the blink was so fast it couldnt be seen and it took me minutes !! to figure out that it WAS working. I had to increase the time to 50000000 to get a good blink. Its blinking now.

  2. krish121 says:

    hi! how can 1.2us time for adc in above code.

    • batchloaf says:

      Hi Krish,

      The 1.2us time that I mention in my comments depends on a few things:

      • As explained in section 20.6 of the dsPIC30F4011 datasheet (“Selecting the A/D conversion clock”), each A/D conversion requires 12 x Tad, where Tad is the analog-to-digital converter clock period.
      • The value of Tad depends on a couple of things – the instruction cycle Tcy (four times the oscillator clock cycle) and the ADC clock prescaler, ADCS, which is configured using the lowest six bits of the ADCON3 register (see line 56 of the example program).
      • In my example, the internal fast RC oscillator (with PLL multiplier enabled) is used to drive the dsPIC, so the oscillator frequency is 120MHz. The dsPIC performs one machine code instruction every four clock cycles, so it performs 30,000,000 instructions per second (i.e. 30 MIPS). This means that the instruction cycle (the time taken to perform one instruction), Tcy = 33.33ns.
      • When the A/D converter is converting the input voltage into a 10-bit binary number, the time taken to calculate each bit is Tad. There’s also a small overhead associated with each conversion which means that the total time taken is actually 12 x Tad.
      • According to the datasheet, in order for each bit to be calculated correctly, Tad should be at least 154ns. The value of Tad depends on the length of the instruction cycle, Tcy, so you can only set Tad once you know what Tcy is (remember that it’s 33.33ns in my example).
      • As stated in section 20.6 of the datasheet, the formula for Tad is as follows: Tad = Tcy * (0.5 * (ADCS + 1))
      • So, once you know Tcy, you basically just need to pick the lowest value of ADCS that gives you a Tad > 154ns. In my configure_pins function, I set ADCS = 5 (the ADCS value is stored in the ADCON3 register, as explained in chapter 20 of the datasheet), which means that Tad = 3 * Tcy = 100ns.
      • As you may have noticed, my value of Tad is therefore less than the recommended minimum of 154ns! I hadn’t realised this until you asked this question, so I may need to update this example. Anyway, I’ll stick with the 100ns value for the rest of this explanation, but you should probably set ADCS to a higher value if you want to be sure of an accurate conversion!
      • When you start the conversion process (which I do by setting ADCON1bits.SAMP = 0 in my read_analog_channel function), the time it will take to complete is 12*Tad = 12*100ns = 1.2us.

      Hopefully that explains it!


      • krish121 says:

        hi! i m using ADCON3=0x0109 as per your calculation its 1.98us bit it takes it iakes 4.08us sampling manully start and end auto end ,then conversion is also automatic.

      • batchloaf says:

        Hi Krish,

        It sounds like the extra time results from several factors:

        • The processing overhead associated with calling the read_analog_channel function,
        • The processing overhead of the other instructions in the read_analog_channel function,
        • The manual sampling delay,
        • And possibly an additional automatic sampling delay.

        The sampling delay is necessary to allow the sampling capacitor to charge up to the same voltage that is connected to the pin. This takes a little time because it is basically a simple RC circuit with associated time constant (T=RC, where C is the 4.4pF sampling capacitor, and R is equal to the sum of the 3.25kOhm resistance of the input circuit and the source resistance of the external analog voltage source). See Figure 20-2 in section 20.7 of the dsPIC30F4011 datasheet for more details of the analog input equivalent circuit.

        I implement the sampling delay manually in my read_analog_channel function. I chose a delay of 1us, which was based on an assumed RC time constant of 200ns, which corresponds to a source impedance of just over 40kOhm. You may have heard of the rule of thumb that an RC circuit reaches steady state after 5 time constants? Well, that’s why I made the sampling time equal to five times the estimated time constant.

        In reality, the required sampling time depends on the source impedance of the sensor (or whatever you have connected to the pin). If the analog voltage source has a very low source impedance (or if you use a unity-gain op-amp buffer for example), a shorter sampling time might be adequate. Conversely, if your analog voltage source has a very high source impedance, even 1us may not be enough for the sampling capacitor voltage to settle to the correct value.

        Also, I see that your ADCON3 value sets SAMC = 1. Is it possible that this activates automatic sampling time? If so, I think the conversion time will be 13 x Tad rather than 12 x Tad.


  3. krish121 says:

    hi! actually i am using Rs=4.7k and start sampling by setting SAMP=1 manually, then sampling will stop automatically and conversion auto trigger , i think it takes 13tad time , total impedance 4.7k(rs)+3.25k=7.95 as per thumb rule it gives value of 174.9ns so 0.174us time . now go for total time 13tad gives 2.145us now if we go for total ime 13 tad plus thumb rule time its value is 2.319us
    now i am confuse how its 4.08us.

  4. krish121 says:

    before calling function read_analog_channel(); i set one i/o pin as logic 1 and at the end of this function that i/o pin will logic zero.

  5. krish121 says:

    before calling function read_analog_channel(); i set one i/o pin as logic 1 and at the end of this function that i/o pin will logic zero. and measure time between gpio toggle.

    • batchloaf says:

      Ok, that’s a good way of doing it. Please try placing the i/o pin set and reset inside the read_analog_channel function to find out how much of the delay is due to the overhead of the function call. If it turns out to be a big part of it, then there are ways you can work around it (e.g. making it an inline function).

  6. amitroy1984 says:

    i am trying to write a program for dspic30f4012.
    I have a requirement to call a adcvalue() function
    within an interrupt routine of timer1. This adcvalue()
    function needs to switch on the ADC. Load the digital
    values of analog signals at AN0 and AN1 to variables
    defined as data1, data2. And then again turn down
    the ADC. This cycle repeats each time within the timer1 ISR.

    Can anyone tell me how to do it.(need to know adc setting and adcvalue function)
    I wrote the following code but it doesnt work.
    //ADC initialisation
    void AD_set() // ***** A to D (A/D) Settings
    ADPCFG = 0x0038; // Configure pins AN(3-5)/RB(3-5) into Digital
    // I/O mode AN(0-2) pins are in Analog mode
    // ADCON1bits.ADSIDL= 0;
    // ADCON1bits.FORM= 0;
    // ADCON1bits.SSRC= 7;
    // ADCON1bits.SAMP= 1;
    ADCON1= 0x00E0;
    ADCHS = 0x0000; //
    ADCSSL = 0x0003;
    // ADCON3bits.SAMC= 8;
    // ADCON3bits.ADRC= 0;
    // ADCON3bits.ADCS= 1;
    ADCON3= 0x0F00;
    // ADCON2bits.VCFG= 0;
    // ADCON2bits.CSCNA= 1;
    // ADCON2bits.SMPI= 1;
    // ADCON2bits.BUFM= 0;
    // ADCON2bits.ALTS= 0;
    ADCON2= 0x0404;
    //ADCON2 = 0x0004; // Interrupt after every 2 samples

    My main prog is as follows

    // main-prog.c
    #include “settings-prog.h”
    _FOSC(CSW_FSCM_OFF & XT); // To use the external crystal
    _FWDT(WDT_OFF); // To disable the watchdog timer
    void send_data(int);
    int AD_value(); // Declare the AD value reading function
    int AD_value1();
    float R, P;
    void main()
    uart_set(); // Initialise UART settings
    AD_set(); // Initialise ADC settings
    timer1_set(T); // Initialise Timer-1 settings & start timer
    TRISEbits.TRISE8 = 0; // RE8 is configured as output

    // Continue until stop the power
    }// End of main()

    // Interrupt service routine (ISR) for interrupt from Timer1

    void __attribute__((interrupt, no_auto_psv)) _T1Interrupt (void)
    IFS0bits.T1IF = 0; // Clear timer 1 interrupt flag
    P= AD_value1();
    uart_tx(9); // Space between 2 pieces of data.
    R= AD_value();
    } // End of ISR of Timer 1

    int AD_value()
    int ADCValue;
    ADCON1bits.ASAM= 1;
    //IEC0bits.ADIE= 0;
    IFS0bits.ADIF= 0;
    ADCON1bits.ADON= 1;
    while (!IFS0bits.ADIF);
    ADCValue= ADCBUF0;
    }// End of AD_value()

    int AD_value1()
    int ADCValue1;
    ADCON1bits.ASAM= 1;
    //IEC0bits.ADIE= 0;
    IFS0bits.ADIF= 0;
    ADCON1bits.ADON= 1;
    while (!IFS0bits.ADIF);
    ADCValue1= ADCBUF1;
    }// End of AD_value()
    void send_data(int s_data)
    int s;
    if(s_data < 0)
    // Send the negative sign (ASCII is 45)
    s_data = -1*s_data;

    // Digit with the position value of 100
    s = s_data/100;

    // Digit with the position value of 10
    s_data = s_data – (s *100);
    s = s_data/10;

    // Digit with the position value of 1
    s_data = s_data – (s *10);
    }// End of send_data()

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s