How to Improve ADC Resolution

joeyd999

Joined Jun 6, 2011
5,283
One Question....
Should I use the mikroC ADC library and do the ADC bit settings separately ?
I have no idea what the MicroC library looks like. All you need to do is write the a/d config registers, set the GO bit, and, in the interrupt, capture the ADRESH/L values. Why one would require a library for this is beyond me.
 

Thread Starter

R!f@@

Joined Apr 2, 2009
9,918
Just asking....some say Mikro Pro's ADC_Read is slow.
Lemme try without the library..Haven't done that, so a learning curve.
 

Thread Starter

R!f@@

Joined Apr 2, 2009
9,918
OK working on it...one more q?
Should the whole ADC reading and accumulating part be in the 256us IRQ or in the main() controlled by a 256us interrupt flag bit ?
 

joeyd999

Joined Jun 6, 2011
5,283
OK working on it...one more q?
Should the whole ADC reading and accumulating part be in the 256us IRQ or in the main() controlled by a 256us interrupt flag bit ?
The reading and accumulation should be done in the IRQ with as few instructions as possible.

Have you decided upon a clock frequency & sample period?
 

Thread Starter

R!f@@

Joined Apr 2, 2009
9,918
I am gonna use 8MHz.
And trying to set up TMR0 to 256us interrupt.
Plus learning not to use mikroC ADC library.

so it might take a while.

Don't exactly understand how to achieve "few instructions" but let me try this first.
 

joeyd999

Joined Jun 6, 2011
5,283
I am gonna use 8MHz.
And trying to set up TMR0 to 256us interrupt.
Plus learning not to use mikroC ADC library.

so it might take a while.

Don't exactly understand how to achieve "few instructions" but let me try this first.
At 8 MHz, each instruction cycle takes 0.5 µS. This means you have 512 instruction cycles between conversions.

There are a minimum of 2 interrupts required each conversion -- one by the timer to start the conversion, and one by the a/d to capture, accumulate, and flag the result.

Each instruction cycle used by either routine subtracts from the total instruction bandwidth of the processor. For instance, if it takes 128 instruction cycles to process the interrupts (including entry, context save, processing, context restore, and return), that uses up 25% of your total instruction cycles in interrupts. This is why we try to keep ISRs short, sweet, and to the point.

Your application is easy. I don't believe you'll be running into any issues.
 

Thread Starter

R!f@@

Joined Apr 2, 2009
9,918
At 8 MHz, each instruction cycle takes 0.5 µS. This means you have 512 instruction cycles between conversions.
I was trying to figure out the 512 instruction part. Took a while but..
8 MHz --> 8/4 = 2MHz gives me 0.5us per Instruction Cycle...so, 256us for ADC means 256us/0.5us = 512 Instruction Cycles.

Clarify me this...., Does this mean that the maximum no. of instructions that can be used in IRQ is 512 ?
 

joeyd999

Joined Jun 6, 2011
5,283
I was trying to figure out the 512 instruction part. Took a while but..
8 MHz --> 8/4 = 2MHz gives me 0.5us per Instruction Cycle...so, 256us for ADC means 256us/0.5us = 512 Instruction Cycles.

Clarify me this...., Does this mean that the maximum no. of instructions that can be used in IRQ is 512 ?
If all your IRQs use all your available instruction cycles, there will be nothing left for your main program. The goal is to do as little processing as possible in the IRQs and as much as possible in main.

With that said, you also need to insure that things get done in a timely fashion so everything stays in sync. For instance, I accumulate in the IRQ because the main loop may not cycle fast enough to accumulate each conversion on its own. Whereas if I accumulate 256 (64*4) conversions over a period of 64 ms, this gives my main loop plenty of time to mozy along doing whatever it has to do before getting back to processing the results.
 

Kjeldgaard

Joined Apr 7, 2016
476
There are a minimum of 2 interrupts required each conversion -- one by the timer to start the conversion, and one by the a/d to capture, accumulate, and flag the result.
I usually do it in a single interrupt. With only a single ADC channel it looks like this:
1: Read the current ADC measurement result.
2: Start the ADC for the next measurement.
3: Return from interrupt.
 

joeyd999

Joined Jun 6, 2011
5,283
I usually do it in a single interrupt. With only a single ADC channel it looks like this:
1: Read the current ADC measurement result.
2: Start the ADC for the next measurement.
3: Return from interrupt.
Good for you.

But that's not what what were doing here.
 

Thread Starter

R!f@@

Joined Apr 2, 2009
9,918
OK..I got a fresh code setup.
8MHz and prescaler 4.
ADC is sampled in TMR0 ISR right now.
Display is updated in the main code.
I can see the LCD read out varying when I rotate ADC pot.

What's Next ?
I cannot figure out how to take exactly 64 readings.
I might need a little bit of xplanation.
I am dummy.
 

joeyd999

Joined Jun 6, 2011
5,283
OK..I got a fresh code setup.
8MHz and prescaler 4.
ADC is sampled in TMR0 ISR right now.
Display is updated in the main code.
I can see the LCD read out varying when I rotate ADC pot.

What's Next ?
I cannot figure out how to take exactly 64 readings.
I might need a little bit of xplanation.
I am dummy.
Post your code so I can see what you are doing.

If you want exactly any number of anything, you usually want to use a counter. There are two ways to do this.

First, let's assume you are processing 4 channels in round-robin fashion: AN0 -> AN1 -> AN2 -> AN3.

The first way is the most "logical". Preset an 8 bit counter to 64. At the end of AN3 processing, decrement the counter and check for 0. If it's zero, copy out the accumulators, zero the accumulators, reset the counter, and set the flag so your main() knows a set of conversions is complete.

The second way is a little less intuitive. Start with your 8 bit counter at 0, and decrement once each conversion regardless of channel. When the counter again reaches zero, you are done. Do the remaining things above, but, this time, you don't need to reset the counter because it is already 0. Why does this work? Because it takes 256 steps to go from 0 to 0 in an 8 bit counter, and you require 64*4 conversions which happens to be 256.
 

Thread Starter

R!f@@

Joined Apr 2, 2009
9,918
C:
sbit LCD_RS at RB1_bit;
sbit LCD_EN at RB0_bit;
sbit LCD_D4 at RC4_bit;
sbit LCD_D5 at RC5_bit;
sbit LCD_D6 at RC6_bit;
sbit LCD_D7 at RC7_bit;
sbit LCD_RS_Direction at TRISB1_bit;
sbit LCD_EN_Direction at TRISB0_bit;
sbit LCD_D4_Direction at TRISC4_bit;
sbit LCD_D5_Direction at TRISC5_bit;
sbit LCD_D6_Direction at TRISC6_bit;
sbit LCD_D7_Direction at TRISC7_bit;
#define TMR0reload 169
unsigned int ADC_Temp, Volts;

char *Vout = "00.00";


void interrupt(){
if(INTCON.T0IF){
  INTCON.T0IF = 0;
  TMR0 += TMR0reload;


   ADC_Temp = ADC_Read(0);

}
}
void main(){
OSCCON = 0x77;
ANSEL = 0x0F;
ANSELH = 0x20;
CM1CON0 = 0x07;
CM2CON0 = 0x07;
PORTA = 0;
PORTB = 0;
PORTC = 0;
TRISA = 0x3F;
TRISB = 0XF0;
TRISC = 0x07;
ADC_Init();
LCD_Init();
Lcd_Cmd(_LCD_CLEAR);
Lcd_Cmd(_LCD_CURSOR_OFF);
OPTION_REG = 0x81;   // Prescaler at 4
INTCON = 0;
PIE1 = 0;
PIE2 = 0;
INTCON.T0IF = 0;
TMR0 = 0;
INTCON.T0IE = 1;
INTCON.GIE = 1;
while(1){
  Volts = ADC_Temp * 2.9296875;  // testing voltage scaling to 30V max

  Vout[0] = Volts/1000+48; // Extract 1000th place
  Vout[1] = (Volts/100)%10+48; // Extract 100th place
  Vout[3] = (Volts/10)%10+48; // Extract 10th place
  Vout[4] = (Volts/1)%10+48; // Extract 1st place
  Lcd_Out(1,10,Vout); // Send to LCD
  Lcd_Out(1,1,"Voltage:" ); // Send to LCD
  Lcd_Out(2,1,"Current:" ); // Send to LCD

}
}
My Test Code
Did not add anything to slow down the reading yet. It can be done later. Since I have a multiturn pot on ADC pin, I can change just one increment right now. so I guess why add anything to slow down LCD
 

Thread Starter

R!f@@

Joined Apr 2, 2009
9,918
The first way is the most "logical". Preset an 8 bit counter to 64. At the end of AN3 processing, decrement the counter and check for 0. If it's zero, copy out the accumulators, zero the accumulators, reset the counter, and set the flag so your main() knows a set of conversions is complete.
I can do this...:D
 

joeyd999

Joined Jun 6, 2011
5,283
C:
sbit LCD_RS at RB1_bit;
sbit LCD_EN at RB0_bit;
sbit LCD_D4 at RC4_bit;
sbit LCD_D5 at RC5_bit;
sbit LCD_D6 at RC6_bit;
sbit LCD_D7 at RC7_bit;
sbit LCD_RS_Direction at TRISB1_bit;
sbit LCD_EN_Direction at TRISB0_bit;
sbit LCD_D4_Direction at TRISC4_bit;
sbit LCD_D5_Direction at TRISC5_bit;
sbit LCD_D6_Direction at TRISC6_bit;
sbit LCD_D7_Direction at TRISC7_bit;
#define TMR0reload 169
unsigned int ADC_Temp, Volts;

char *Vout = "00.00";


void interrupt(){
if(INTCON.T0IF){
  INTCON.T0IF = 0;
  TMR0 += TMR0reload;


   ADC_Temp = ADC_Read(0);

}
}
void main(){
OSCCON = 0x77;
ANSEL = 0x0F;
ANSELH = 0x20;
CM1CON0 = 0x07;
CM2CON0 = 0x07;
PORTA = 0;
PORTB = 0;
PORTC = 0;
TRISA = 0x3F;
TRISB = 0XF0;
TRISC = 0x07;
ADC_Init();
LCD_Init();
Lcd_Cmd(_LCD_CLEAR);
Lcd_Cmd(_LCD_CURSOR_OFF);
OPTION_REG = 0x81;   // Prescaler at 4
INTCON = 0;
PIE1 = 0;
PIE2 = 0;
INTCON.T0IF = 0;
TMR0 = 0;
INTCON.T0IE = 1;
INTCON.GIE = 1;
while(1){
  Volts = ADC_Temp * 2.9296875;  // testing voltage scaling to 30V max

  Vout[0] = Volts/1000+48; // Extract 1000th place
  Vout[1] = (Volts/100)%10+48; // Extract 100th place
  Vout[3] = (Volts/10)%10+48; // Extract 10th place
  Vout[4] = (Volts/1)%10+48; // Extract 1st place
  Lcd_Out(1,10,Vout); // Send to LCD
  Lcd_Out(1,1,"Voltage:" ); // Send to LCD
  Lcd_Out(2,1,"Current:" ); // Send to LCD

}
}
My Test Code
Did not add anything to slow down the reading yet. It can be done later. Since I have a multiturn pot on ADC pin, I can change just one increment right now. so I guess why add anything to slow down LCD
Just FYI: You are stalling your code in the timer interrupt routine. By asking for a read on channel 0, you are setting the mux, starting the conversion, and waiting for the result. You are burning lots of cycles, even more so if you chose a long acquisition time.

Again: start the conversion in the interrupt (the mux should have already been preset during the last a/d interrupt). Read the results upon the a/d interrupt, and change the channel there.
 

joeyd999

Joined Jun 6, 2011
5,283
Vout[0] = Volts/1000+48; // Extract 1000th place
Vout[1] = (Volts/100)%10+48; // Extract 100th place
Vout[3] = (Volts/10)%10+48; // Extract 10th place
Vout[4] = (Volts/1)%10+48; // Extract 1st place

I see this a lot around here. It'll likely work no problem in your simple app. Just know that there are far more efficient ways to convert binary to BCD.
 

Thread Starter

R!f@@

Joined Apr 2, 2009
9,918
Just FYI: You are stalling your code in the timer interrupt routine. By asking for a read on channel 0, you are setting the mux, starting the conversion, and waiting for the result. You are burning lots of cycles, even more so if you chose a long acquisition time.

Again: start the conversion in the interrupt (the mux should have already been preset during the last a/d interrupt). Read the results upon the a/d interrupt, and change the channel there.
As said before reading mikroC library is not good. I figured that. But for now I like to learn how to average. If it works I can work on the burning cycle part


Vout[0] = Volts/1000+48; // Extract 1000th place
Vout[1] = (Volts/100)%10+48; // Extract 100th place
Vout[3] = (Volts/10)%10+48; // Extract 10th place
Vout[4] = (Volts/1)%10+48; // Extract 1st place
I see. it works.

I think we will try that later :)

C:
#define TMR0reload 169
#define ADCreload 64
unsigned long int ADC_Vtemp, ADC_V;
unsigned int ADC_Temp, Volts;
unsigned char ADC_Loop_Counter;

char Display;

char *Vout = "00.00";


void interrupt(){
if(INTCON.T0IF){
  INTCON.T0IF = 0;
  TMR0 += TMR0reload;

  if(ADC_Loop_Counter){         // If loop counter is not zero
    ADC_Vtemp += ADC_Read(0);   // Read ADC and keep adding to accumulator(temp)
    --ADC_Loop_Counter;         // Decremen loop counter
    if(ADC_Loop_Counter == 0){  // If loop counter is 0
      ADC_Vtemp = ADC_V;        // Copy accumulator
      ADC_Vtemp = 0;            // Clear accumulator
      ADC_Loop_Counter = ADCreload; // reload loop counter
      Display = 1;                // set coonversion flag
     }
  }


}
}
void main(){
OSCCON = 0x77;
ANSEL = 0x0F;
ANSELH = 0x20;
CM1CON0 = 0x07;
CM2CON0 = 0x07;
PORTA = 0;
PORTB = 0;
PORTC = 0;
TRISA = 0x3F;
TRISB = 0XF0;
TRISC = 0x07;
ADC_Init();
LCD_Init();
Lcd_Cmd(_LCD_CLEAR);
Lcd_Cmd(_LCD_CURSOR_OFF);

Display = 0;
ADC_Vtemp = 0;
ADC_V = 0;
ADC_Loop_Counter = ADCreload;
OPTION_REG = 0x81;   // Prescaler at 4
INTCON = 0;
PIE1 = 0;
PIE2 = 0;
INTCON.T0IF = 0;
TMR0 = 0;
INTCON.T0IE = 1;
INTCON.GIE = 1;
while(1){
if(Display){
  Display = 0;
  ADC_Temp = ADC_V/8;
  Volts = ADC_Temp * 2.9296875;  // testing voltage scaling to 30V max

  Vout[0] = Volts/1000+48; // Extract 1000th place
  Vout[1] = (Volts/100)%10+48; // Extract 100th place
  Vout[3] = (Volts/10)%10+48; // Extract 10th place
  Vout[4] = (Volts/1)%10+48; // Extract 1st place
  Lcd_Out(1,10,Vout); // Send to LCD
  Lcd_Out(1,1,"Voltage:" ); // Send to LCD
  Lcd_Out(2,1,"Current:" ); // Send to LCD
  }
Now the LCD is reading 00.00. :oops:
I believe I have a scaling issue still.
Is the interrupt part OK ? :D
 

Thread Starter

R!f@@

Joined Apr 2, 2009
9,918
C:
unsigned long int ADC_Vtemp, ADC_V;
These 4 byte integers. I think I do not need that more bits
16 bit will do right ?
 

joeyd999

Joined Jun 6, 2011
5,283
As said before reading mikroC library is not good. I figured that. But for now I like to learn how to average. If it works I can work on the burning cycle part


I see. it works.

I think we will try that later :)

C:
#define TMR0reload 169
#define ADCreload 64
unsigned long int ADC_Vtemp, ADC_V;
unsigned int ADC_Temp, Volts;
unsigned char ADC_Loop_Counter;

char Display;

char *Vout = "00.00";


void interrupt(){
if(INTCON.T0IF){
  INTCON.T0IF = 0;
  TMR0 += TMR0reload;

  if(ADC_Loop_Counter){         // If loop counter is not zero
    ADC_Vtemp += ADC_Read(0);   // Read ADC and keep adding to accumulator(temp)
    --ADC_Loop_Counter;         // Decremen loop counter
    if(ADC_Loop_Counter == 0){  // If loop counter is 0
      ADC_Vtemp = ADC_V;        // Copy accumulator
      ADC_Vtemp = 0;            // Clear accumulator
      ADC_Loop_Counter = ADCreload; // reload loop counter
      Display = 1;                // set coonversion flag
     }
  }


}
}
void main(){
OSCCON = 0x77;
ANSEL = 0x0F;
ANSELH = 0x20;
CM1CON0 = 0x07;
CM2CON0 = 0x07;
PORTA = 0;
PORTB = 0;
PORTC = 0;
TRISA = 0x3F;
TRISB = 0XF0;
TRISC = 0x07;
ADC_Init();
LCD_Init();
Lcd_Cmd(_LCD_CLEAR);
Lcd_Cmd(_LCD_CURSOR_OFF);

Display = 0;
ADC_Vtemp = 0;
ADC_V = 0;
ADC_Loop_Counter = ADCreload;
OPTION_REG = 0x81;   // Prescaler at 4
INTCON = 0;
PIE1 = 0;
PIE2 = 0;
INTCON.T0IF = 0;
TMR0 = 0;
INTCON.T0IE = 1;
INTCON.GIE = 1;
while(1){
if(Display){
  Display = 0;
  ADC_Temp = ADC_V/8;
  Volts = ADC_Temp * 2.9296875;  // testing voltage scaling to 30V max

  Vout[0] = Volts/1000+48; // Extract 1000th place
  Vout[1] = (Volts/100)%10+48; // Extract 100th place
  Vout[3] = (Volts/10)%10+48; // Extract 10th place
  Vout[4] = (Volts/1)%10+48; // Extract 1st place
  Lcd_Out(1,10,Vout); // Send to LCD
  Lcd_Out(1,1,"Voltage:" ); // Send to LCD
  Lcd_Out(2,1,"Current:" ); // Send to LCD
  }
Now the LCD is reading 00.00. :oops:
I believe I have a scaling issue still.
Is the interrupt part OK ? :D
You don't need if(ADC_Loop_Counter).

Every conversion gets accumulated, regardless of the state of the accumulator.

ADC_V is going to be a 16 bit unsigned fixed point fraction. Does C natively know how to handle such a data type? I don't think so.

So, actual V=ADC_V*Vref/65536

You Vref is 2.9296875?

Scaled up by a factor of 1000:

V=ADC_V*2929.6875/65536

Watch your type casting to ensure a valid result.
 
Top