Skip to content

Instantly share code, notes, and snippets.

@bigjosh
Last active April 7, 2022 09:51
Show Gist options
  • Save bigjosh/ae9ffcd9fd0da9a25807 to your computer and use it in GitHub Desktop.
Save bigjosh/ae9ffcd9fd0da9a25807 to your computer and use it in GitHub Desktop.

Revisions

  1. bigjosh revised this gist Nov 7, 2014. 1 changed file with 1 addition and 7 deletions.
    8 changes: 1 addition & 7 deletions NoPartsBatteryGauge.c
    Original file line number Diff line number Diff line change
    @@ -63,7 +63,7 @@ uint8_t readVccVoltage(void) {

    // Enable ADC, set prescaller to /8 which will give a ADC clock of 1mHz/8 = 125kHz

    ADCSRA |= _BV(ADEN) | _BV( ADSC) | _BV(ADPS1) | _BV(ADPS0);
    ADCSRA = _BV(ADEN) | _BV(ADPS1) | _BV(ADPS0);

    /*
    After switching to internal voltage reference the ADC requires a settling time of 1ms before
    @@ -72,17 +72,11 @@ uint8_t readVccVoltage(void) {
    */

    _delay_ms(1);


    /*
    The first conversion after switching voltage source may be inaccurate, and the user is advised to discard this result.
    */

    ADCSRA |= _BV(ADSC); // Start 1st conversion

    while( ADCSRA & _BV( ADSC) ) ; // Wait for 1st conversion to be ready...

    // Start a Conversion

    ADCSRA |= _BV(ADSC); // Start a conversion

  2. bigjosh revised this gist Nov 7, 2014. 1 changed file with 4 additions and 4 deletions.
    8 changes: 4 additions & 4 deletions NoPartsBatteryGauge.c
    Original file line number Diff line number Diff line change
    @@ -47,7 +47,7 @@ time and an initial system clock prescaling of 8, resulting in 1.0 MHz system cl
    // For faster readings, you could initialize once, and then take multiple fast readings, just make sure to
    // disable the ADC before going to sleep so you don't waste power.

    uint16_t readVccVoltage(void) {
    uint8_t readVccVoltage(void) {

    // Select ADC inputs
    // bit 76543210
    @@ -108,9 +108,9 @@ uint16_t readVccVoltage(void) {

    // Compute a fixed point with 1 decimal place (i.e. 5v= 50)
    //
    // Vcc = (1.1v * 1024) / ADC
    // Vcc100 = ((1.1v * 1024) / ADC ) * 10 ->convert to 1 decimal fixed point
    // Vcc100 = ((11 * 1024) / ADC ) ->simplify to all 16-bit integer math
    // Vcc = (1.1v * 1024) / ADC
    // Vcc10 = ((1.1v * 1024) / ADC ) * 10 ->convert to 1 decimal fixed point
    // Vcc10 = ((11 * 1024) / ADC ) ->simplify to all 16-bit integer math

    uint8_t vccx10 = (uint8_t) ( (11 * 1024) / adc);

  3. bigjosh created this gist Nov 7, 2014.
    166 changes: 166 additions & 0 deletions NoPartsBatteryGauge.c
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,166 @@
    /*
    * NoPartsBatteryGuageAVR.c
    *
    * This is a simplified demonstration of how to detect power supply voltage by using the on chip
    * analog-to-digital converter to measure the voltage of the internal band-gap reference voltage.
    *
    * The code will read the current power supply voltage, and then blink an LED attached to pin 6 (PA7).
    *
    * 1 blink = 1 volts <= Vcc < 2 volts (only applicable on low voltage parts like ATTINY84AV)
    * 2 blinks = 2 volts <= Vcc < 3 volts
    * 2 blinks = 2 volts <= Vcc < 3 volts
    * 3 blinks = 3 volts <= Vcc < 4 volts
    * 4 blinks = 4 volts <= Vcc < 5 volts
    * 5 blinks = 5 volts <= Vcc
    *
    * 0 blinks = power supply turned off :)
    *
    * This code was tested on an ATTINY84A with default fuse settings, but should work unchanged on
    * any ATTINYx4 or ATTINYx4A, and should be easily ported to any 8-bit AVR with ADC and internal 1.1 reference voltage.
    *
    * More info at...
    *
    * http://wp.josh.com/2014/11/06/battery-fuel-guage-with-zero-parts-and-zero-pins-on-avr/
    *
    */


    //Comments in slash/asterisk form are quoted from the datasheet

    /*
    The device is shipped with CKSEL = “0010”, SUT = “10”, and CKDIV8 programmed. The default
    clock source setting is therefore the Internal Oscillator running at 8.0 MHz with longest start-up
    time and an initial system clock prescaling of 8, resulting in 1.0 MHz system clock.
    */

    #define F_CPU 1000000

    #include <avr/io.h>
    #include <util/delay.h>


    // Returns the current Vcc voltage as a fixed point number with 1 implied decimal places, i.e.
    // 50 = 5 volts, 25 = 2.5 volts, 19 = 1.9 volts
    //
    // On each reading we: enable the ADC, take the measurement, and then disable the ADC for power savings.
    // This takes >1ms becuase the internal reference voltage must stabilize each time the ADC is enabled.
    // For faster readings, you could initialize once, and then take multiple fast readings, just make sure to
    // disable the ADC before going to sleep so you don't waste power.

    uint16_t readVccVoltage(void) {

    // Select ADC inputs
    // bit 76543210
    // REFS = 00 = Vcc used as Vref
    // MUX = 100001 = Single ended, 1.1V (Internal Ref) as Vin

    ADMUX = 0b00100001;

    /*
    By default, the successive approximation circuitry requires an input clock frequency between 50
    kHz and 200 kHz to get maximum resolution.
    */

    // Enable ADC, set prescaller to /8 which will give a ADC clock of 1mHz/8 = 125kHz

    ADCSRA |= _BV(ADEN) | _BV( ADSC) | _BV(ADPS1) | _BV(ADPS0);

    /*
    After switching to internal voltage reference the ADC requires a settling time of 1ms before
    measurements are stable. Conversions starting before this may not be reliable. The ADC must
    be enabled during the settling time.
    */

    _delay_ms(1);


    /*
    The first conversion after switching voltage source may be inaccurate, and the user is advised to discard this result.
    */

    ADCSRA |= _BV(ADSC); // Start 1st conversion

    while( ADCSRA & _BV( ADSC) ) ; // Wait for 1st conversion to be ready...

    // Start a Conversion

    ADCSRA |= _BV(ADSC); // Start a conversion


    while( ADCSRA & _BV( ADSC) ) ; // Wait for 1st conversion to be ready...
    //..and ignore the result


    /*
    After the conversion is complete (ADIF is high), the conversion result can be found in the ADC
    Result Registers (ADCL, ADCH).
    When an ADC conversion is complete, the result is found in these two registers.
    When ADCL is read, the ADC Data Register is not updated until ADCH is read.
    */

    // Note we could have used ADLAR left adjust mode and then only needed to read a single byte here

    uint8_t low = ADCL;
    uint8_t high = ADCH;

    uint16_t adc = (high << 8) | low; // 0<= result <=1023

    // Compute a fixed point with 1 decimal place (i.e. 5v= 50)
    //
    // Vcc = (1.1v * 1024) / ADC
    // Vcc100 = ((1.1v * 1024) / ADC ) * 10 ->convert to 1 decimal fixed point
    // Vcc100 = ((11 * 1024) / ADC ) ->simplify to all 16-bit integer math

    uint8_t vccx10 = (uint8_t) ( (11 * 1024) / adc);

    /*
    Note that the ADC will not automatically be turned off when entering other sleep modes than Idle
    mode and ADC Noise Reduction mode. The user is advised to write zero to ADEN before entering such
    sleep modes to avoid excessive power consumption.
    */

    ADCSRA &= ~_BV( ADEN ); // Disable ADC to save power

    return( vccx10 );

    }


    int main(void)
    {

    // Enable output for LED pin

    DDRA |= _BV(PORTA7);


    while(1)
    {

    // Read the current Vcc voltage as a 2 decimal fixed point value

    uint8_t vccx10 = readVccVoltage();

    // Convert to whole integer value (rounds down)

    uint8_t vcc = vccx10 / 10;

    // Indicate the Vcc voltage by blinking the LED Vcc times...

    for(int i=0;i<vcc;i++) {

    PORTA |= _BV(PORTA7); // Turn on LED

    _delay_ms(250);

    PORTA &= ~_BV(PORTA7); // LED off

    _delay_ms(250);

    }

    _delay_ms(1000); // Pause before next round

    }
    }