I'm trying to read a temperature sensor (Lm35) from my pic16f887 ADC. The thing is I'm getting a difference of about +/- 15 ticks in the results. Is this expected? Should I be doing the mean of several acquisitions?

The Lm35 gives temperature in mv, where 10mv = 1 celsius degree. The range goes from (20mv to 1.5v), roughly 2º to 150º. Because this in theory the ADC has an accuracy of 0.5º. (1024 ticks = 5v from which we get 307 ticks = 1.5v and since this is 150 degrees 150/307 = 0.5º).

To improve on this half a degree accuracy I've added a 2.5v zener diode to vref+ with a 10uc capacitor. Thus the final circuit I have is this.


simulate this circuit – Schematic created using CircuitLab

But I'm still getting variatons of +-15 ticks variatons, which is a lot in degrees!

And the code I'm using is this.

#include <xc.h>
#include <pic16f887.h>

#define _XTAL_FREQ 8000000

#pragma config FOSC = INTRC_CLKOUT, WDTE = OFF, PWRTE = OFF      
#pragma config MCLRE = ON, CP = OFF, CPD = OFF, BOREN = OFF      
#pragma config IESO = ON, FCMEN = ON, LVP = OFF        

typedef unsigned int uint;
typedef unsigned char uchar;
typedef unsigned short ushort;

void uart_init(const long int baudrate) {
    SPBRG = 12;
    TXEN = 1;
    SPEN = 1;
    SYNC = 0;
    TRISC6 = 0;
    TRISC7 = 1;

void uart_write(char data) {
    while (!TRMT) {}
    TXREG = data;

uchar uart_tx_empty() {
    return (uchar)TRMT;

void uart_write_l(char *text, uint size) {
    for (int i = 0; i < size; i++) {

ushort read_adc() {
    ADON = 1;
    GO_DONE = 1;
    while (GO_DONE) {}
    ADON = 0;
    ushort res = 0;
    res = ADRESH << 8;
    res |= ADRESL;
    return res;

void main(void) {
    ANS0 = 1;
    TRISA0 = 1;
    PORTD = 0; // Set led port
    TRISD = 0; // LED Output
    OSCCON = 0x70; // 8mhz internal clock
    ADFM = 1;  // right justified
    VCFG0 = 1; // Vref+
    ADCON0 = 0b11000000; 
    while (1) {
        ushort result = read_adc();
        char *arr = (char *)&result;
        uart_write_l(arr, 2);  

        RD1 = 1;
        RD1 = 0;

  • \$\begingroup\$ The LM35 sensor does feature 10-mV/°C sensitivity. But the ±0.5°C initial accuracy is only at 25°C. It is ±0.75°C accuracy over its range, I think. It is a good part that incorporates a lot of good ideas. But it is only rated to drive 50pF. Also observe the line and load regulation specs and see how this may impact results. All this assumes a perfect ADC and VREF, which you decidedly DO NOT HAVE. Take a careful look at the ADC noise specifications and your VREF noise, as well. All in all, I suspect that your results are "expected" taking all aspects into account. \$\endgroup\$
    – jonk
    Commented Dec 16, 2017 at 21:55
  • 1
    \$\begingroup\$ How long is the cable to your sensor? You cannot ignore the cable capacitance if the cable is more than about a foot or two, or you will get oscillation. \$\endgroup\$ Commented Dec 16, 2017 at 22:14
  • \$\begingroup\$ @jonk: No, +-15 counts is well beyond reasonable noise for this A/D. He's getting the large noise due to blatant acquisition time violations. \$\endgroup\$ Commented Dec 16, 2017 at 22:49
  • \$\begingroup\$ May I just add that resolution and accuracy is not the same thing? What is stated as the accuracy of the measurement (in degrees Celsius/AD-step) is not the accuracy, but resolution. The accuracy is the device's ability to represent the correct process value. \$\endgroup\$
    – MrGerber
    Commented Dec 16, 2017 at 23:27
  • \$\begingroup\$ Link that explains the difference between accuracy, resolution, precision and sensitivity: kb.mccdaq.com/KnowledgebaseArticle50043.aspx \$\endgroup\$
    – MrGerber
    Commented Dec 16, 2017 at 23:33

3 Answers 3


You need to actually read the datasheet, particularly the chapter on the A/D. Two obvious problems from a quick look at the code are that you aren't giving the A/D time to stabalize after turning it on, and you aren't giving it any acquisition time.

Try leaving the A/D on all the time. That gets around both problems, assuming you leave enough time after one conversion before starting the next. It seems you have some delays in there, so this condition is met.

Again, how to use the A/D properly is well described in the datasheet. Read it.

Once you get it basically working, you can do some low pass filtering on the readings to reduce the remaining one or two counts of noise about the average.

I usually run the A/D faster than I need from interrupt code. This also low pass filters the sequence of readings, then leaves the filtered value in a global variable that the foreground code can grab whenever it wants to. That way the A/D readings and usage of the values are decoupled, allowing each piece of code to do the one thing it does well, and be reasonably independent of other parts of the system. Everything intertwined, as in your code, will get you into trouble when you try to grow the system beyond the simplest of demos.

  • \$\begingroup\$ By stabilization time you meant the equations from section 9.3? I thought that was when changing A/D channels. I also had a delay between the ON AND GO bits of about 100ms, but for some (unknown) reason I didn't include it in the code when I put it here. But looking at the datasheet again it should be 11Tads (2-6uC at 8mhz using FRC) + 11.5 uC (TACQ in table 17-10) = 78uC to be safe. Right? Before that delay (100ms) the program wasn't working at all. I'll try following your advice and leave the A/D on all the time. \$\endgroup\$
    – aram
    Commented Dec 16, 2017 at 23:55
  • \$\begingroup\$ @Aram: Micro-Coulombs don't make any sense here. \$\endgroup\$ Commented Dec 17, 2017 at 12:43
  • \$\begingroup\$ My bad, again :/. I meant us. \$\endgroup\$
    – aram
    Commented Dec 17, 2017 at 19:34

Two things to try:

Add a delay between ADON = 1 and GO_DONE = 1. This will allow the internal switches to settle before reading the ADC.

If this solves your problem, you may still see one-two "ticks" of uncertainty. You can further reduce this by taking multiple readings and averaging. I usually sum 8 readings and right-shift the answer 3 bits.

Also, from the data sheet, It looks like the LM35 has an emitter follower output and doesn't like capacitive loads. They recommend adding a load as shown:

enter image description here

  • \$\begingroup\$ There is a delay of about 100ms between ADON and GO_DONE. For some (unknow reason I didn't put it when pasting my code). \$\endgroup\$
    – aram
    Commented Dec 16, 2017 at 23:56
  • \$\begingroup\$ I added a picture in my answer above. \$\endgroup\$ Commented Dec 18, 2017 at 14:37

To improve on this half a degree accuracy I've added a 2.5v zener diode

Zener diodes below 5V tend to have poor regulation and temperature stability.

enter image description here

The TL431B 'precision shunt regulator' would be a much better choice. It has 0.5% accuracy and very low drift at normal room temperatures.

I'm getting a difference of about +/- 15 ticks in the results. Is this expected?

Sounds a little high, but not surprising if there is some noise on the signal as well as the reference and power supply. You can probably filter it out by taking an average of several readings.

My usual technique is to accumulate 64 10 bit readings (producing a 16 bit result without overflow) then divide the total back down to the resolution I want. It is often possible to increase the effective resolution beyond 10 bits, as the noise 'dithers' the readings to produce intermediate values when averaged.


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.