Fyzikální kabinet FyzKAB
Články Modul Arduino (něco navíc) Arduino měří své napájecí napětí

Arduino měří své napájecí napětí

Jak jsme již poznali v článku Skrytý teploměr v Arduino, v mnoha AVR čipech (používané pro moduly Arduino) je vnitřní napěťová reference 1,1 V. Měření této reference lze kupříkladu využít pro zlepšení přesnosti standardní funkce modulu Arduino – analogRead(). Druhou možností využití této reference je změření hodnoty napájecího napětí Ucc, což lze využít pro sledování stavu napájecí baterie bez zbytečného použití vstupního analogového pinu. Na oba výše uvedené způsoby využití se podíváme, protože jak uvidíme, hodně spolu souvisí.

Arduino meri Ucc

Zpřesnění funkce analogRead()

Běžným předpokladem načítání analogové hodnoty napětí na vstupním analogovém pinu (použití funkce analogRead()) je použití referenčního napětí 5,0 V, které je však bráno z napájecího napětí. Pokud však naše napájení není dokonale regulováno nebo pokud naše zapojení pracuje na baterie, může se toto napětí od hodnoty 5 V dost lišit.

Je tedy jasné, že aby bylo možné přesně měřit analogové napětí, potřebujeme přesnou referenci napětí. Většina čipů AVR umožňuje využívat tři možné referenční zdroje – interní zdroj 1,1 V (některé mají interní zdroj napětí 2,56 V), externí referenční napěťový zdroj nebo právě zmíněné napájecí napětí Ucc.

Jak jsme si naznačili, napájecí napětí Ucc může být v některých případech zcela nedůvěryhodné. Externí referenční napětí je pochopitelně nejpřesnější, ale vyžaduje další hardware, takže se zaměříme na poslední možnost s 1,1V referencí zabudovanou na čipu.

V případě reference 1,1 V však není situace také kdoví jaká. Interní reference je sice stabilní, ale má asi 10% odchylku své hodnoty, takže (dle závislosti použitého čipu) se ani na přesnost hodnoty 1,1 V nemůžeme spolehnout. Navíc většinou budeme chtít měřit v širším rozsahu než jen 0–1,1 V.

Hledáme řešení…

Shrňme si tedy situaci:

  • Napájecí napětí Ucc je pro měření z hlediska rozsahu dobré, ale nemůžeme se na něj příliš spoléhat, protože se může s postupným vybíjením baterie měnit.
  • Vnitřní reference je stabilní, ale její velká tolerance značně omezuje přesnost.

Jak tedy za těchto podmínek něco pořádně změřit? Pokud víme, že vnitřní reference je stabilní, můžeme s její pomocí změřit napájecí napětí. Jen nebudeme přesně vědět, zda tato hodnota odpovídá realitě. Máme tu tedy dvě hodnoty, hodnotu napájecího napětí a vnitřní napěťovou referenci. A jsme schopni určit vztah mezi nimi!

Budeme-li jednu z těchto hodnot skutečně znát, můžeme tu druhou spočítat. Nabízí se tedy možnost změřit napájecí napětí (pomocí stabilní interní 1,1V reference) a zároveň určit tuto hodnotu pomocí voltmetru. Napájecí napětí je přece na modulu Arduino pro měření dostupné. Pomocí znalosti přesné hodnoty napájecího napětí, lze zpětně určit hodnotu pevné napěťové reference. A dále… pokud budeme znát referenční napětí konkrétního čipu, můžeme pak již podle ní určovat velikost napájecího napětí. A to i v případě, že se bude měnit při postupném vybíjení baterii. A jelikož již dokážeme určovat aktuální napájecí napětí, budeme vědět, jakou referenci využívá funkce analogRead().
Jak prosté!

Určení napětí vnitřní 1,1V reference

Nejdříve si vytvoříme funkci pro určení napájecího napětí pomocí 1,1V reference. Ve výchozím stavu budeme předpokládat hodnotu vnitřní reference 1,1 V. Výslednou hodnotu, kterou modul Arduino změří a vypíše na sériový monitor, si zapíšeme. Pak pomocí voltmetru změříme napájecí napětí na modulu Arduino (napětí mezi piny +5V a GND). I tuto hodnotu si zapíšeme a později použijeme pro úpravu kódu. Začneme první verzí programu pro načtení hodnoty napájecího napětí pomocí vnitřní reference, o které zatím předpokládáme, že má hodnotu 1,1 V.

První část funkce readVcc() nastavuje dle použitého AVR čipu modulu Arduino měření reference a napájecího napětí. Podobný kód jsme viděli při používání vnitřního teplotního čidla v předchozím článku věnovanému skrytému vnitřnímu čidlu čipů AVR. (můžete tuto část srovnat s kódem článku o vnitřním teplotním čidle):

long readVcc() {
    // nacteni 1,1V reference proti Vcc
    // nastaveni referenci na Vcc a mereni na interní referenci 1,1V
    #if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
          ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
    #elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
          ADMUX = _BV(MUX5) | _BV(MUX0);
    #elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
          ADMUX = _BV(MUX3) | _BV(MUX2);
    #else
          ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
    #endif

    delay(2);    // Počkejte, až se Vref usadí
    ADCSRA |= _BV(ADSC);    // Start prevodu
    while (bit_is_set(ADCSRA,ADSC));    // mereni

    uint8_t low = ADCL;    // musí nejprve nacist ADCL - pak uzamkne ADCH
    uint8_t high = ADCH;    // odemkne obojí

    long result = (high<<8) | low;

    float scale_constant = 1125300L;    // 1125300 = 1.1*1023*1000   


    result = scale_constant / result;    // Vypocita Vcc (v mV); 1125300 = 1.1*1023*1000

    return result;    // Vcc v milivoltech
}

void setup() {
    // put your setup code here, to run once:
    Serial.begin(9600);
}

void loop() {
    // put your main code here, to run repeatedly:
    Serial.print("Napajeci napeti je ");
    Serial.print(readVcc() );
    Serial.println(" mV");
    delay(1000);
}

Funkce readVcc() nyní navrací hodnotu napájecího napětí v milivoltech. Hlavní nekonečná smyčka Loop() tuto hodnotu v sekundových intervalech měří a vypisuje na sériový výstup. Představme si, že tato hodnota je kupříkladu 4892 mV.

Nyní přistoupíme k změření napájecího napětí. Připojený voltmetr naměřil mezi piny +5V a GND hodnotu 4,900 V, což odpovídá 4900 mV.

Vidíme, že se tyto hodnoty zase tolik neliší. Ale i tak 4900 přece není 4892! Potřebujeme tedy hodnotu vnitřní reference trochu korigovat. To uděláme přepočítáním hodnoty reference pomocí konstanty internal1_1Ref, kterou pak použijeme po přidání do funkce readVcc().

float internal1_1Ref = 1.1 * 4900 / 4892;    // napeti na voltmetru / napeti dle readVcc pred upravou

Vidíme, že jde o hodnotu domnělé reference 1,1 V vynásobenou skutečnou hodnotou napájecího napětí a vydělenou hodnotou zatím určenou funkcí readVcc(). V tomto konkrétním případě to tedy určuje, že skutečná hodnota reference je asi 1,1018 V. Fajn, měli jsme štěstí na zakoupený modul Arduino, protože vnitřní 1,1V reference se zrovna od nominální hodnoty moc příliš neliší. Budete mít takové štěstí také? 😉

Nyní se podíváme na konstantu scale_constant, která nám z hodnoty referenčního napětí vypočítává napětí napájecí. Její hodnota, jak vidíme i v poznámce předchozího kódu (vyznačeno podbarvením), se skládá ze součinu velikosti retenčního napětí a hodnoty 1023 (maximální hodnota naměřená 10bitovým AD převodníkem čipu). Další násobení 1000 je jen z důvodů převodu výstupní hodnoty na milivolty.

Když nyní známe přesnou hodnotu retenčního napětí (konstanta internal1_1Ref), stačí vypočítat scale_constant pomocí této hodnoty:

float scale_constant = internal1_1Ref * 1023 * 1000; // zkorigovana konstatnta Vref

Tyto dvě řádky nyní stačí vyměnit ve funkci readVcc() za původní výpočet scale_constant.
(v následujícím kódu opět barevně podbarveno)

Hotový kód včetně provedené kalibrace Uref :

long readVcc() {
    // nacteni 1,1V reference proti Vcc
    // nastaveni referenci na Vcc a mereni na interní referenci 1,1V
    #if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
          ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
    #elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
          ADMUX = _BV(MUX5) | _BV(MUX0);
    #elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
          ADMUX = _BV(MUX3) | _BV(MUX2);
    #else
          ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
    #endif

    delay(2);    // Počkejte, až se Vref usadí
    ADCSRA |= _BV(ADSC);    // Start prevodu
    while (bit_is_set(ADCSRA,ADSC));    // mereni

    uint8_t low = ADCL;    // musí nejprve nacist ADCL - pak uzamkne ADCH
    uint8_t high = ADCH;    // odemkne obojí

    long result = (high<<8) | low;

    float internal1_1Ref = 1.1 * 4900 / 4892;    // napeti na voltmetru / napeti dle readVcc pred upravou
    float scale_constant = internal1_1Ref * 1023 * 1000;    // zkorigovana konstatnta Vref


    result = scale_constant / result;    // Vypocita Vcc (v mV); 1125300 = 1.1*1023*1000

    return result;    // Vcc v milivoltech
}

void setup() {
    // put your setup code here, to run once:
    Serial.begin(9600);
}

void loop() {
    // put your main code here, to run repeatedly:
    Serial.print("Napajeci napeti je ");
    Serial.print(readVcc() );
    Serial.println(" mV");
    delay(1000);
}

Závěr

Jakmile máme zkalibrovanou funkci readVcc() otevírá se nám dvojí možnost jejího využití – korekce funkce analogRead(), pokud chceme změřit konkrétní napětí na vstupu, nebo sledování hodnoty napájecího napětí na modulu Arduino. Obojí se totiž odvíjí od znalosti hodnoty napájecího napětí.

Arduino meri Ucc - vystup na serial monitoru
Výstup měřeného napájecího napětí modulu Arduino (zde vytištěno ve voltech)

Určitou nevýhodu tohoto řešení je nutnost nastavení hodnoty internal1_1Ref u každého konkrétního modulu Arduino (popř. AVR čipu), takže nelze program „jen tak“ přenášet z modulu na modul. Na druhou stranu je to určitá daň za to, že získáme možnost využívání modulu Arduino zase v trochu jiných dimenzích. Jednou z možností je kupříkladu použití AVR čipu jako přesného voltmetru. Jistě neocenitelnou možností je i možnost sledování napájecího napětí při provozu z baterie.

Další konkrétní využití této funkce je již na vývojáři samotném.

UPOZORNĚNÍ:
Nesouhlasíme s vyřazením Newtonových zákonů, Ohmova zákona a zákona zachování energie z učiva fyziky základních škol v České republice!