Search |
 |
|
 |
| Author |
Message |
|
|
Posted: May 20, 2012 - 01:44 PM |
|

Joined: May 20, 2012
Posts: 4
Location: Peak District, UK
|
|
Hello my names andrew and i've been teaching myself about C programming and avr's on and off for the past few months. this is my first proper project apart from flashing leds etc.
Project;
A tiny13 charlieplexed battery gauge for my friends R/C car. Using 6 leds of appropriate colours to show power left in batteries.
Operation;
A potential divider reduces 11.1v nominal battery to ~1v to feed adc Pinb4 which is sampled 10/second. A array stores the LED status. A timer interupt scans the array choosing which led to light as only one can be lit at a time, which are on Pinb0-Pinb2.
The problem;
Works perfectly when the adc ref is set to vcc and using a potentiometer, but when changed in code to internal 1.1v ref LEDS flicker wildly hardly ever show a steady state.
I've tried feeding the adc pin from a 1.5v battery and potentiometer to isolate any possible supply ripple, also 5v supply to avr is decoupled with a big capacitor.
Can anyone suggest things to try to stop the irratic readout when ref is internal 1.1v?
Also i stole a lot of the code from Eloque's great tut on here, and anajonsr's awesome vidoe's on youtube!
P.S. I dont get to a computer very often...
Here is the code....
Code:
/*..........Charlieplexing Battery Guage...........*/
#define LED1on 864
#define LED2on 884
#define LED3on 891 //adc count at which leds switch on
#define LED4on 903 //0>1023 = 0>ref volts
#define LED5on 924
#define LED6on 942
#define ADCfire 627 /*9.6mhz(clock) /255(timer overflow) /6(No of leds) /627 = 1/10
second, counts up to 65535 possible (unsigned int) */
#include<avr/io.h>
#include<util/delay.h> //Header files
#include<stdlib.h>
#include<avr/interrupt.h>
void checkLED (void);
void setresult (int ADCcount);
void setled (char nLED); //Define prototype for setled function
void setpowerguage (char npower); //Define prototype for setpowerguage function
char aLEDarray [6] = {0}; //Array to hold led status
uint8_t ncurrentLED; //defining variable ncurrentLED
unsigned int ADCtick; //counts up to 65535 possible
int main(void) //Main program
{
DDRB = 0b000000; //All ports data direction to input
PORTB = 0b000000; //All ports to 0
sei();
ADCSRA |= 1<<ADPS1; //PRESCALER 64. 150K = @ 9.6MHZ
ADCSRA |= 1<<ADPS2; //PRESCALER 64. 150K = @ 9.6MHZ
ADMUX |= 1<<MUX1 ; //ADC INPUT PB4 IC PIN3
ADMUX |= 1<<REFS0 ; //ADC VOLTAGE REFERENCE INTERNAL(COMMENT OUT FOR EXTERNAL)
ADCSRA |= 1<<ADEN ; //ENABLE ADC
TIMSK0 |= 1<<TOIE0; //enable CTC interupt
ADCSRA |= 1<<ADIE ; //INTERUPT ENABLE
ADCSRA |= 1<<ADSC ; //START CONVERSION
TCCR0A &= ~1<<WGM00; //wave generation mode 0 (0 by default)
TCCR0B |= 1<< CS00; //timer prescaler to scan leds,fires interupt 37647 times a second
while (1) //Endless loop
{
}
}
ISR(TIM0_OVF_vect)
{
DDRB = 0b000000; //set all ports to 0 ready for new reading
PORTB = 0b000000;
checkLED(); //call checkled function
}
ISR(ADC_vect) //adc interupt vector
{
uint8_t thelow = ADCL; //reading lowest 8 bits of adc
uint16_t tenbitvalue =ADCH << 8 | thelow; //convert ADC to 10 bits
setresult (tenbitvalue); //call setresult with tenbitvalue
}
void setresult (int ADCcount) //funtion setresult and its varible ADCcount
{
if (ADCcount >=LED6on)
setpowerguage(6);
else if(ADCcount >=LED5on) //check which setpowerguage number to call
setpowerguage(5);
else if(ADCcount >=LED4on)
setpowerguage(4);
else if(ADCcount >=LED3on)
setpowerguage(3);
else if(ADCcount >=LED2on)
setpowerguage(2);
else if(ADCcount >=LED1on)
setpowerguage(1);
else
setpowerguage(0);
}
void setpowerguage (char npower) //Function setpowerguage and its varible npower
{
ncurrentLED = 0;
for (uint8_t i = 0; i < 6; ++i) //Reset all of array to 0 using a loop
{
aLEDarray[i] = 0;
}
for (uint8_t i = 0; i < npower; ++i) //filling array with required leds to be lit
{
aLEDarray [i] = 1;
}
}
void checkLED (void) //function checkLED called from timer interupt vector
{
if (aLEDarray[ncurrentLED]==1) // checking against ncurrentLED which led needs to be lit
{
setled(ncurrentLED); //send led number to setled
ncurrentLED ++; // advance to next led
}
else
{
ncurrentLED++;
}
if(ncurrentLED == 6) //if current led count = 6 reset ncurrentLED.
{
ncurrentLED = 0;
if(ADCtick == ADCfire)//9.6mhz(clock) / 255(timer overflow) / 6(No of leds) / 627 = 1/10 second
{
ADCSRA |= 1<<ADSC; //scan led's Xmany times then start new conversion
ADCtick = 0; //reset ADCcount
}
ADCtick ++;
}
}
void setled (char nLED) //Function setled and its varible nLED,
{
switch (nLED)
{
case 0:
DDRB = 0b000011; //switch case argument for each led according to nLED
PORTB = 0b000001;
break;
case 1:
DDRB = 0b000110;
PORTB = 0b000010; // lighting leds sequentially 1 3 2 4 5 6 (PCB design)
break;
case 2:
DDRB = 0b000011;
PORTB = 0b000010;
break;
case 3:
DDRB = 0b000110;
PORTB = 0b000100;
break;
case 4:
DDRB = 0b000101;
PORTB = 0b000100;
break;
case 5:
DDRB = 0b000101;
PORTB = 0b000001;
break;
}
} [/quote]
edited: 3/6/2012 reason: title change. |
Last edited by Chipit on Jun 02, 2012 - 01:20 PM; edited 1 time in total
|
| |
|
|
|
|
|
Posted: May 20, 2012 - 02:01 PM |
|

Joined: May 20, 2012
Posts: 4
Location: Peak District, UK
|
|
| Just had a idea that maybe continually setting the adc pin to a input in setled, is not a good idea, but i would have thought when the adc samples the processor would be concerned with the adc and not be switching ports on and off would it not? |
|
|
| |
|
|
|
|
|
Posted: May 21, 2012 - 03:08 AM |
|

Joined: Mar 19, 2003
Posts: 736
|
|
Here are a few suggestions.
First, the internal 1.1V "reference" is pretty poor as far as references go. You will be much happier using a good regulator/reference for VCC, and use VCC as your A/D reference. Getting references with initial tolerances of 3%,1%, and 0.5% is routine. Make sure that you get one with enough current capability to run your micro. FYI - this on-board reference issue is not unique to Atmel. The voltage reference accuracy of most micros with on-board references are fair to poor.
Second, generally speaking, measuring battery voltage can be done relatively slowly. So, to get the best A/D accuracy, run your A/D clock slowly (check datasheet for limits).
Third, take multiple battery voltage samples, then average. You can easily take 16,32, or 64 samples, sum, then average. Up to 64 sample can be added to a summation, and still stay within a 16 bit word (using the full 10 bit A/D result). |
|
|
| |
|
|
|
|
|
Posted: May 21, 2012 - 04:25 AM |
|

Joined: Aug 07, 2007
Posts: 1477
Location: Czech
|
|
Is there a capacitor 100nF on the AREF pin?
For test I would also try to filter the measured voltage with say 10 kOhm/1uF.
I do not think the internal reference is as miserable as gahelton wrote. Of course, it can differ 10% from 1.1V. You have to measure the actual value on the AREF pin and use it for computations.
I think it is sufficient for battery measuring.
Edit:
Quote:
Works perfectly when the adc ref is set to vcc
Then why not use this (with a propper divider). |
|
|
| |
|
|
|
|
|
Posted: Jun 02, 2012 - 02:40 PM |
|

Joined: May 20, 2012
Posts: 4
Location: Peak District, UK
|
|
Almost sorted! My main problem turned out to be noise on the power lines upsetting the uC. the big electrolytic across the input wasn't able to filter out the noise from a dc-dc converter i was using to power the programmer, which had been powering the circuit, although the programmer has a LDO regulator it is actually bridged so it has no effect, you live and learn!
Thank you for all the suggestions,
Works perfectly when the adc ref is set to vcc
Then why not use this (with a propper divider).
Because i'm stubborn! But i will keep it in mind.
I've implemented the averaging, presume this is how you meant me to do it?
Code:
void callADC (void)
{
ADCresult = 0;
DDRB = 0b000000; //set all ports to 0 ready for new reading
PORTB = 0b000000;
for (uint8_t i=0; i<16; i++) //take 16 conversions
{
ADCSRA |= 1<<ADSC; //start new conversion
while (bit_is_clear(ADCSRA,ADIF)); wait until conversion completes & flag is 1
uint8_t thelow = ADCL; //reading lowest 8 bits of adc
uint16_t tenbitvalue = ADCH << 8 | thelow; //convert ADC to 10 bits
ADCresult = ADCresult + tenbitvalue; //add result
}
setresult (ADCresult >> 4); //right shift adcresult 4 places, divide by 16, call setresult.
}
Turning the LEDs off before taking a measurment improves things even more.
Finally i wuold like to learn how to use the ADC noise reduction mode, more out of curiosity and learning something new than absolute necesity. In the data sheet it seems to say, enable sleep mode, and then when sleep instruction is executed, adc will complete, and fire the adc interupt which wakes the processor back up.
But mine never wakes up. What could i be doing wrong?
Code:
for (uint8_t i=0; i<16; i++) //take 16 conversions
{
MCUCR |= 1<<SE; //ENABLE SLEEP MODE
MCUCR |= 1<<SM0; //SLEEP MODE ADC NOISE REDUCTION
while (bit_is_clear(ADCSRA,ADIF));
uint8_t thelow = ADCL; //reading lowest 8 bits of adc
uint16_t tenbitvalue = ADCH << 8 | thelow; //convert ADC to 10 bits
ADCresult = ADCresult + tenbitvalue; //add result
}
|
|
|
| |
|
|
|
|
|
Posted: Jun 02, 2012 - 07:55 PM |
|

Joined: Aug 07, 2007
Posts: 1477
Location: Czech
|
|
I have tested this
Code:
EMPTY_INTERRUPT (ADC_vect);
uint16_t adc_get(void)
{
sleep_enable();
ADCSRA |= (1 << ADSC); //start adc
sleep_cpu(); //go sleep
sleep_disable(); //after wakeup
return ADC;
}
uint16_t adc_value;
char tempstr[10];
int main()
{
set_sleep_mode (SLEEP_MODE_ADC);
// ADC enable, prescaler = 128
ADCSRA = (1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);
// reference voltage = AVCC
// input = ADC5 (portc.5)
ADMUX = (1<<REFS0) | 5;
ADCSRA |=(1<<ADIE); //enable ADC interrupt
uart_init();
sei();
while(1)
{
adc_value = adc_get();
itoa(adc_value,tempstr,10);
uart_puts(tempstr);uart_puts("\r\n");
_delay_ms(1000);
}
}
|
|
|
| |
|
|
|
|
|
Posted: Jun 02, 2012 - 08:31 PM |
|

Joined: May 20, 2012
Posts: 4
Location: Peak District, UK
|
|
Ah!
so a conversion has to be started before entering noise reduction mode, i will try that thank you. |
|
|
| |
|
|
|
|
|
|
|
|