Do tej pory zepsute lipole zdarzały mi się co kilka miesięcy. Zawsze scenariusz był taki sam. Co jakiś czas sprawdzałem miernikiem napięcie na baterii i wszystko było w porządku. Nagle robot przestaje działać. Nie wiem co jest powodem i próbuję zdebugować, ale nic się nie udaje. Dopiero po jakimś czasie sprawdzam napięcie na baterii i jest już rozładowana grubo poniżej limitu. Próbuję jeszcze ją podładować, ale się nie udaje. Kolejny pakiet do wyrzucenia. Jednak od dziś to się zmieni. Teraz mój soft jest wyposażony w procedurę ochrony baterii i nie dopuści do nadmiernego rozładowania. W tym artykule opiszę dokładnie jak działa ta procedura.

Charakterystyka akumulatora lipol

Nazwa lipol (li-pol, lipo) pochodzi od materiałów, z których akumulator jest zbudowany – z litu oraz polimerów. Lipole w porównaniu ze zwykłymi bateriami, czy ogniwami Lion (litowo – jonowe) mniej ważą oraz zajmują mniej miejsca dla takich samych pojemności. Można więc powiedzieć, że mają dużą gęstość energii. Z tego powodu są chętnie stosowane w robotyce oraz wszelkiego rodzaju dronach i pojazdach RC. Minusem lipoli jest niebezpieczeństwo, że się zapali w przypadku niewłaściwego użytkowania.

Pojedyncze ogniwo lipol posiada maksymalne napięcie 4.1 – 4.2 V, napięcie znamionowe 3.6 – 3.7 V i napięcie minimalne około 3 V. Oznacza to, że w pełni naładowane ogniwo ma około 4.2 V, podczas pracy jego napięcie dosyć szybko spada do około 3.6 V i utrzymuje się na tej wartości przez dłuższy czas. Następnie gdy jest już mocno rozładowane szybko traci napięcie. Jeśli spadnie ono poniżej 3 V, istnieje ryzyko uszkodzenia ogniwa. Uszkodzonego ogniwa nie da się z powrotem naładować, a w skrajnych przypadkach może nawet wybuchnąć. Charakterystykę napięciową ogniwa obrazuje poniższy wykres.

Ogniwa można łączyć w pakiety szeregowo lub równolegle tworząc pakiety o wyższym napięciu i większej pojemności. Najpopularniejsze w robotyce i RC pakiety to 2S (8.2 V) i 3S (12.3 V). Zainteresowanych większą dawką wiedzy teoretycznej o lipolach odsyłam do świetnej serii na Forbocie.

Monitorowanie stanu baterii

W swoim robocie używam ogniwa 2S o maksymalnym napięciu 8.2 V. Próg minimalny poniżej którego chcę chronić pakiet ustaliłem na 7 V. Jest to wartość poniżej napięcia znamionowego, która pozostawia jeszcze pewien zapas do napięcia minimalnego dając więcej czasu na interwencję. Do pomiaru napięcia użyję wbudowanego w STMa przetwornika analogowo-cyfrowego (ADC). I tutaj pojawia się pierwszy problem – na ADC mogę podać maksymalnie napięcie 3.3V. Wyższe napięcie może spalić procesor.

Przewidziałem taką sytuację projektując płytkę i do monitorowania stanu baterii wykorzystałem dzielnik napięcia.

Wykorzystane przeze mnie rezystancje to 51 k\Omega i 100 k\Omega . Wartość progowa dla 12-bitowego ADC można obliczyć z wzoru:

\frac{R_{low} \cdot V_{BATmin} \cdot ADC_{VALmax}}{V_{ADCmax} \cdot (R_{low} + R_{high})} = ADC_{thr}

Po podstawieniu daje to:

\frac{51 k\Omega \cdot 7.0 V \cdot 4095}{3.3 V \cdot 151 k\Omega} = 2933

Procedura obsługi

Procedurze programowej obsługi monitorowania baterii postawiłem następujące wymagania:

  • Włączenie procedury maksymalnie ogranicza zużycie energii – wyłączenie silników i LEDów.
  • Wyraźna sygnalizacja kiedy działa poprawnie, a kiedy jest oszczędzanie energii – LED świeci przy normalnym działaniu, gaśnie przy rozładowaniu.
  • Ignoruje fałszywe alarmy – napięcie poniżej progu musi się utrzymywać przez określony czas zanim przejdziemy do procedury ochrony baterii. Czas ten ustaliłem na 0.5 s.

Ostatecznym efektem jest taki kod:

PRIVATE void vbat_task(void *params)
{
    (void) params;

    tick_t tick_cnt;
    int32_t vbat_val;
    int32_t low_voltage_timeout;

    low_voltage_timeout = 0;
    while (1)
    {
        tick_cnt = rtos_tick_count_get();

        vbat_val = adc_val_get(ADC_VBAT);
        if (vbat_val < (int32_t)VBAT_THRESHOLD)
        {
            led_off(LED_VBAT);

            low_voltage_timeout++;
            if (5 <= low_voltage_timeout)
            {
                rtos_critical_section_enter();
                motor_all_off();
                led_all_off();

                while (1)
                {
                    TEST_ENDLESS_LOOP();
                }
            }
        }
        else
        {
            led_on(LED_VBAT);

            low_voltage_timeout = 0;
        }

        rtos_delay_until(&tick_cnt, 100);
    }
}

Gotowy kod jeszcze sprawdziłem na rzeczywistym hardware, żeby zobaczyć, czy faktycznie wykryje za niskie napięcie. Do testów użyłem zasilacza laboratoryjnego i moduł działa bardzo dobrze. Szczególnie zadowolony jestem z odrzucania fałszywych alarmów. W miarę zbliżania się do progu po diodzie widzę, że na krótko wahania napięcia występują, jednak dopiero po przekroczeniu progu na stałe włącza się ochrona baterii.

Unit testy modułu

Napisanie procedury chroniącej baterię było też dla mnie okazją do sprawdzenia w akcji frameworka CppUTest. Po raz pierwszy stosowałem framework C++ do testowania kodu w C. Wbudowanego mechanizmu mockowania z CppUTest nie wykorzystałem, bo chciałem najpierw zobaczyć jak w C++ pójdzie pisanie własnych mocków. Także czas powstawania modułu vbat trochę się wydłużył przez zabawy z frameworkiem testowym i ustanowienie odpowiednich konwencji do pisania unit testów. Przynajmniej ta praca została już wykonana i dodawanie kolejnych unit testów będzie łatwiejsze. Do tej pory głównie robiłem moduły bazujące na HW, więc TDD nie przyniosłoby takich korzyści. Czujniki ścian to ostatni element hardware do ogarnięcia, więc do kolejnych modułów unit testy się bardziej przydadzą.

Podsumowanie

W końcu doczekałem się modułu chroniącego baterię. Do tego czasu zdążyłem zniszczyć chyba 5 lipoli. Lepiej późno niż wcale.  Mam nadzieję, że od teraz będą mi służyć dłużej niż kilka miesięcy i nie będę musiał mieć zapasów, żeby czekanie na wysyłkę nie stopowało prac.