wtorek, 1 listopada 2016

O przewadze zmywarki nad ręcznym myciem garów - biblioteki cd ..

Biblioteka TIMERS.H z daleka i bliska

Jakiś czas temu dotknąłem pobieżnie tematu bibliotek tych genialnych narzędzi programisty I choć to podstawa całej filozofii klocków lego, o której opowiada ten blog to jednak wszystko jest jeszcze przede mną. Zdałem sobie sprawę z tego analizując moją najbardziej ulubioną bibliotekę Timers.h.


We wszystkich programach i tych uruchamianych na Arduino UNO/NANO i tych dla ESP8266 biblioteka Timers.h jest podstawowym zawiadowcą całym programem. Odpowiada zarówno za bezpieczeństwo systemu wymuszając resety ESP i samego Atmela (taki programowy watchdog) i obsługuje główne, zależne od czasu, procedury programu a w tym np. częstotliwość komunikacji z BLYNK.
Jest przy tym nieprawdopodobnie prosta, niezawodna i co ważne - nie wchodzi w konflikt z żadnymi elementami sprzętu czy programu. Po prosu ideał. Ale jak to jest w większością ideałów - dostrzega się to dopiero po czasie .....

Analizując wcześniejsze programy dostrzegam w ilu miejscach przegapiłem możliwość zastosowania tej biblioteki. Zamiast tego pracowicie dziergałem  pętle if....  if .... sprawdzające kolejne warunki liczników zdarzeń.

Świetny przykład  opisu i zastosowania tej biblioteki podał wojtekizk  (pewnie to jego biblioteka choć nie pisze o tym wprost :) ) w programie wytrawiarki. Mamy tu praktyczny przegląd możliwości zastosowania tej biblioteki dla obsługi różnorakich zdarzeń powiązanych z czasem.

Jest tylko jedno ale. Zabrakło tu opisu najważniejszej moim zdaniem funkcji tej biblioteki  - zaszytej wewnątrz funkcji setInterval . Działa podobnie jak funkcja updateInterval ale dodatkowo  zeruje licznik początkowy danego timera co ma kapitalne znaczenie dla nowych zastosowań tej biblioteki.

A biblioteka Timers.h potrafi naprawdę znacznie więcej niż to do czego ją wykorzystywałem do tej pory.

1.Obsługa cykliczna zdarzeń. Najbardziej oczywiste zastosowanie.

#include <Timers.h>
Timers <2> akcja; 
void setup(){
   akcja.attach(0, 1000, zrob_cos_co1sek);  // inicjalizacja procesu
}
 void loop(){
 akcja.process();  //pooling - cyklicznie wywołanie by sprawdzić czy już czas działać
}
void zrob_cos_co1sek() {
//wlasny podprogram do wykonania co 1 sek
}


Wywołanie podprogramu zrob_cos następuje co 1 sek - np będzie to zmiana stanu leda na przeciwny, wyświetlenie wartości odczytanej z czujnika itd itd  Uzyskuję w ten sposób obsługę zdarzeń o częstotliwości dostosowanej do rzeczywistych potrzeb. Umieszczenie programu zrob_coś w pętli głównej  programu spowoduje, że wykonywał się on będzie  (niepotrzebnie) setki lub tysiące razy na sekundę.  Możemy go spowolnić  dodając nieśmiertelne delay(), ale zablokuje to możliwość wykonania innych pożytecznych rzeczy przez procesor. Timers.h rozwiąże ten problem za mnie - odczeka w tle 1  sek nie wstrzymując działania innych fragmentów kodu po czym w odpowiednim momencie uruchomi program zrob_coś.

2.Obsługa cykliczna zdarzeń z dynamiczną zmianą częstotliwości obsługi. To odmiana pkt. 1 pozwalająca na zmianę częstości wywołania programu zrob_cos w trakcie działania programy np. zwiększenia szybkości migania led jako sygnalizacja nowego stanu w procesorze.


#include <Timers.h>
Timers <2> akcja; 
void setup(){
   akcja.attach(0, 1000, zrob_cos);  // inicjalizacja procesu
}
 void loop(){
 akcja.process();  //pooling - cyklicznie wywołanie by sprawdzić czy już czas działać
if (zmiana stanu przycisku)  {
      if (zmiana z 0>1) akcja.updateInterval(0,500); else akcja.updateInterval(0, 1000); 
  }
 
}
void zrob_cos() {
//wlasny podprogram do wykonania cyklicznie
}


 Świetnie nadaje się np. jako wskaźnik błedu/błędów w działaniu programu z wykorzystaniem tylko jednego LEDa. Zaraz implementuję to w nowych programach do sterowania led_OK.

3. Zatrzymanie/ start cyklicznej obsługi zdarzeń. To w zasadzie pkt.2 z z ustawionym czasem obsługi = 0.

#include <Timers.h>
Timers <2> akcja; 
void setup(){
   akcja.attach(0, 1000, zrob_cos);  // inicjalizacja procesu
}
 void loop(){
 akcja.process();  //pooling - cyklicznie wywołanie by sprawdzić czy już czas działać
if (zmiana stanu przycisku)  {
      if (zmiana z 0>1) akcja.updateInterval(0,0); else akcja.updateInterval(0, 1000); 
  }
}
void zrob_cos(){
//wlasny podprogram do wykonania cyklicznie
}


 To świetna sprawa - np. uruchamiam wykonywanie większości procedur głównego programu dopiero po nawiązaniu połączenia z BLYNK ale pozostała część odpowiedzialna za reset ESP czy NANO działa nadal nieprzerwanie.

4 Praca monostabilna - jednokrotne odmierzanie zadanego odcinka czasu. To moim zdaniem jedno z ciekawszych możliwości zastosowań tej do bólu prostej biblioteki

#include <Timers.h>
Timers <2> akcja; 
int i=1;

void setup(){
   akcja.attach(0, 0, zrob_cos);  // inicjalizacja procesu
}
 void loop(){
akcja.process();  //pooling - cyklicznie wywołanie by sprawdzić czy już czas działać
if (i) akcja.setInterval(0,5000); // zrob cos za 5 sek
}
void zrob_cos()  {
akcja.updateInterval(0,0); //wyłącz timer 
i=0;
//wlasny podprogram do wykonania jednokrotnie 
}

W momencie PZ następuje jakieś zdarzenie i uruchamiany jest timer na zadany odcinek czasu (koniecznie wywołaniem funkcji setInterval  bo wtedy mamy pewność, że timer odliczy dokładnie zadany odcinek czasu). Po jego upływie timer wywołuje procedurę zrob_cos, która wstrzymuje dalsze odliczanie timera i wykonuje zadany nasz program. Znakomicie nadaje się to do uruchamiania np. pompy obiegowej CO.

Co ważne odcinek czasu na jaki uruchamiany  jest timer może być dowolnie zmieniamy podczas każdorazowego uruchamiania timera a także w trakcie odliczania .
W programie możemy zadecydować  o tym czy kolejny sygnał sterujący w czasie pracy timera  będzie uwzględniony czy nie w procesie odliczania odcinka czasu. Możemy także  sterować zachowaniem timera w zależności od tego czy do zmiany czasu w trakcie odliczania użyjemy funkcji  setInterval   czy też updateInterval . Tym samy dostajemy arcyciekawe możliwości zastosowania biblioteki Timers.h w pracy monostabilnej: 

  1. Praca monostabilna ze stałym czasem - gdy kolejny sygnał wzbudzający timer pojawiający się w trakcie odliczania nie przedłuża odcinku czasu
  2. Praca monostabilna z odświeżaniem czasu - gdy kolejny sygnał wzbudzający timer pojawiający się w trakcie odliczania  przedłuża odcinku czasu o kolejną tą samą wartość.
  3. Praca monostabilna ze zminą czasu - gdy kolejny sygnał wzbudzający timer pojawiający się w trakcie odliczania  przedłuża odliczany okres  nową wartość.
  4. Praca monostabilna ze zminą czasu 2 - gdy kolejny sygnał wzbudzający timer pojawiający się w trakcie odliczania  zmienia odliczany okres. Jeśli timer odliczył już czas nowo ustawianej wartości następuje natychmiastowe wywołanie funkcji zrob_co. Gdy odliczany odcinek czasu jest krótszy niż  nowa wartość, timer "dolicza czas do nowej wartości i wywołuje funkcję zrob_cos.
  5. Praca monostabilna z możliwością zatrzymania timera w trakcie odliczania 
  6. Itd .............

Jak na tak prostą bibliotekę ilość możliwych do wykorzystania opcji rozrasta się imponująco.

Biblioteka ta przypomina mi nieśmiertelny timer NE555 królujący od lat w rożnych aplikacjach częstotliwościowo/czasowych i dla którego wciąż pojawiają  się nowe ciekawe zastosowania.

Z pobieżnego tylko oglądu tej biblioteki widać już jak bardzo potrafi ona ułatwić i uprzyjemnić obsługę wszelkiej maści zdarzeń w tworzonych programach.

Oczywiście nie ma nic za darmo. Wykorzystanie tej biblioteki wiąże się z pewnymi ograniczeniami w programie. Jednak

Nasz program musi pozwolić na wywołanie funkcji akcja.process() w pętli loop na tyle często by nie "przegapić" momentu czasu w którym powinien być uruchomiany podprogram zrob_coś. Jeśli pozostała część programu wykonuje się np w ok 100 ms to wywołanie podprogramu może się w najgorszym razie opóźnić o te 100 ms . Ale dodanie w programie funkcji delay() o czasach rzędu sek lub dłużej całkowicie zaburza działanie tego mechanizmu. A więc stosowanie funkcji delay() lub procedur wstrzymujących obieg programu w pętli głównej jest niedopuszczalne - o ile chcemy mieć pewność, że nastąpi wywołanie procedury zrób_coś w przewidzianym przez nas czasie. Oczywiście taki sam skutek będzie miało wstrzymanie obiegu programu przez program zrob_coś.  
Generalna zasada przy pracy z obsługą typu pooling - często sprawdzać warunek czy działać a jeśli jest spełniony - szybko zrobić co ma się do zrobienia.Często na pierwszy rzut oka nie widać czy dana procedura nie wstrzymuje obiegu programu np w obsłudze czujników przetworników czy modułów komunikacji. Warto taką niesprawdzoną procedurę obsługi umieścić na próbę w pętli głównej i zobaczyć ile razy zostanie ona wywołana w czasie sek. Np.dla czujnika DS18B20 czas przetwarzania pomiaru dla rozdzielczości 12 bitów wynosi ok 750 ms. Jeśli procedura obsługi czujnika wstrzyma obieg programu do czasu uzyskania wyniku pomiaru możemy zablokować sobie cały program tym opóźnieniem. Biblioteka <DallasTemperature.h> pozwala wyłączyć to opóźnienie poprzez wywołanie   sensors.setWaitForConversion(false);
Podobna historia dotyczy przetworników A/C i modułów komunikacji. Wszędzie tam musimy sprawdzić czy nie następuje zawieszenie programu do czasu wykonania przez moduł działania.
W tym kontekście wielką niewiadomą jest BLYNK. W normalnej pracy nie widać w działaniu programu  znaczących opóźnień wynikających z obsługi biblioteki BLYNK.  Ale przy utracie łączności z serwerem lub utracie połączenia wifi cały nasz program się sypie. Opóźnienia wprowadzane przez BLYNK całkowicie dezorganizują pracę np. timera o czasie 1 sek i krótszym.

Ale pomimo tych (przyznajmy niewielkich) ograniczeń biblioteka Timers.h stała się nieodzownym elementem wszystkich moich programów.

Zobaczymy czy po odliczeniu kolejnego odcinka czasu wywoła procedurę ........ cdn();

7 komentarzy:

  1. Czy ta biblioteka może służyć do sterowania p.w.m?

    OdpowiedzUsuń
  2. Witam
    W przykładzie 4.Praca monostabilna wywala mi bład
    "request for member 'attach' in 'akcja', which is of non-class type 'volatile int'"

    OdpowiedzUsuń
    Odpowiedzi
    1. uzupełniłem ci przykład 4 o wszystkie potrzebne warunki. Musi działać

      Usuń
  3. Witam,
    Nie działa funkcja setInterval, może jakiś prosty przykład testowy ?
    Dzieki

    OdpowiedzUsuń
  4. Korzystam z rozwiązania nr 4, wykonuje się operacja po uruchomieniu programu przyciskiem. Odbywa się losowanie po odmierzonym czasie przechodzi do punktu wyjścia.
    I tu pojawia się problem, po uruchomieniu programu kolejny raz nie losuje się nic. Pomaga dopiero restart Arduino.
    Jak usunę akcja.updateInterval(0,0); //wyłącz timer to losowanie odbywa się za każdym razem po uruchomieniu programu.

    Czy jest jakaś funkcja, która pozwoli na uruchamianie losowania za każdym razem jak uruchomię program?

    OdpowiedzUsuń