1
\$\begingroup\$

I decided to start using MPLAB with XC8 after I started reaching the 2k code size limit in MikroC. The problem is that since all the Mikroc libraries are proprietary and there are no libraries available for the PIC16F series in MPLAB, I had to write my own ADC functions.

These are the two functions I wrote to set up my ADC using the datasheet:

void ADC_Init(){
    PIR1bits.ADIF = 0; // Clear interrupt flag
    PIE1bits.ADIE = 0; // Disable ADC interrupt
    INTCONbits.PEIE = 0; // Disable perriferal interrupts

    ADCON2 = 0; // No ADC Trigger

    ADCON1bits.ADCS = 0b111; // Select internal RC clock source
    ADCON1bits.ADPREF = 0b00; // Select VDD as Vref
    ADCON1bits.ADFM = 0; // Left justify, upper 8 MSB in ADRESH

    ADCON0bits.ADON = 1; // Turn on ADC Module
}

int ADC_Read(char channel){
    ADCON0bits.CHS = channel; // Set channel, must be between 0 and 11
    __delay_us(5); // Wait some arbitrary acquisiton time

    PIR1bits.ADIF = 0; // Clear interrupt flag
    ADCON0bits.GO_nDONE = 1; // Start acquisioin

    while(ADCON0bits.GO_nDONE) {} // Wait for the conversion to finish

    return (ADRESH << 2) | ADRESL;

}

The input signal that I'm reading in is pretty noisy to begin with. In my MikroC program with the library ADC functions, the noise floor on the samples ranged between 60 and 100 (10-bit ADC). By noise floor, I mean the range of values the ADC was reading with no input. Noise from the rest of the system I suppose.

In MPLAB with the above functions, the noise floor has shot up 200 to 300. On a 10-bit ADC, this much noise is unacceptable. I run the ADC_Init() function once outside my main program loop, then use the ADC_Read(..) function in my program loop.

Any ideas on why the noise is so much more? Or any ideas on how MikroC get this performance? I suspect it has something to do with the way I configured the ADC.

EDIT

In response to Olin's answer, I believe the 5us "acquisition time" might be a misnomer, that delay is there to allow the holding capacitor to charge after changing ADC channels. The datasheet shows an example for a 10kohm signal impedance needing 7.4us charge time. My signal impedance is only 700ohms, so I figured 5us should be more than enough. As for the circuit, it has not changed, only the software has. And the signal I'm measuring is a slightly noisy digital signal coming out of a MSGEQ7 filter IC.

\$\endgroup\$

2 Answers 2

4
\$\begingroup\$

This is a good illustration of the trouble you run into when using canned libraries.

If you originally had 10% noise on the signal, then maybe the problem is with the circuit. What kind of signal are you measuring? What is its upper frequency? How often are you sampling? It would be useful to show the schematic.

You seem to be allowing 5 µs for acquisition, but how do you know that is enough? What impedance is the signal being fed to the A/D? One possible explanation of your symptoms is that the signal impedance is too high, and the previous code used a longer acquisition time, which allowed the sample and hold to settle more closely to the correct value.

However, a better answer is probably to re-think the whole A/D strategy in the first place. Don't base it on what library routines happen to be available or some similar nonsense. Read the datasheet section on the A/D and think about how best to use it, possibly in conjunction with other hardware, like timers, to solve the overall measurement problem.

A strategy I often use is to take A/D readings in a periodic interrupt. You run this as fast as the A/D will let you acquire and convert the signal, and within limits of leaving enough foreground cycles for the rest of the system. This A/D interrupt samples the A/D much faster than you really need, then low pass filters the results. When the main code wants the latest reading, it doesn't go out and take a single A/D reading. It just uses the current value of the low pass filter output. I have done this sort of thing many times. In fact, it's my normal way to take A/D readings unless there is a specific reason to do it differently.

In a small microcontroller, there is no substitute for actually understanding the hardware and being aware of how exactly it is being used, whether it is thru your own code or "library" routines.

Added about periodic A/D interrupts

You now say you are using periodic interrupts to read the A/D, but then your code makes even less sense than it did before. If you are only reading a single channel, then you'd leave the A/D set to that channel and not change it during normal operation. If you are reading multiple channels, the interrupt would cycle thru them. In that case, the A/D conversion done interrupt would switch the hardware to the new channel right after grabbing the conversion results. That way most of the time that isn't conversion is used to acquire the next signal. Even if you were to run the A/D quite fast, like 100 kHz for example, that still leaves most of 10 µs for the acquisition.

You wouldn't generally call a A/D conversion routine from a interrupt anyway, and even if you did, it wouldn't be changing the channel and then waiting around for the acquisition and conversion while the foreground code is locked out of the processor. That makes no sense and is unnecessary.

Then there is the issue of returning the unsigned 10 bit value left justified into a signed integer. It makes no sense that values of 512 and above should be interpreted as negative.

Once again, what is the upper frequency content of the signal? How often does the firmware need its value? I ask these questions for good reasons, and I expect you to answer them whether you think they are relevant or understand the reasons or not. After all, this is your problem. We are volunteers trying to help, but if you refuse to cooperate then we will find other places to spend our limited time here.

\$\endgroup\$
2
  • \$\begingroup\$ I added an edit to my post based on your answer. I actually am using a similar technique where the ADC is sampled on periodic interrupts and a simple complimentary filter is used to LPF the signal. Despite that, it still doesn't explain why the ADC would be reading much higher values with no input than it previously was. \$\endgroup\$
    – Shubham
    Jul 10, 2014 at 17:11
  • \$\begingroup\$ @Shub: That still doesn't answer the other questions. The code you show makes no sense for a interrupt since it changes the channel only on request, then does a busy wait. A periodic interrupt would change the channel in the interrupt for the previous conversion. Then the A/D gets most of the interrupt period for the acquisition. \$\endgroup\$ Jul 10, 2014 at 17:29
1
\$\begingroup\$

There's nothing inherent to any of the libraries that should impact the noise floor. The default state for many PIC ADC's is 8 bits, and you need to configure things to get the full 10 bits (I don't know how that's done in MPLAB). This may have been handled for you in MikroC. Also, you need to make sure that your data is justified correctly in the 16-bit integer. If you have it left justified, and it used to be right justified, your noise floor would look big

\$\endgroup\$

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.