Jump to content
Behlur_Olderys

Okresowe wołanie funkcji w Arduini

Recommended Posts

Hej,

Chciałem się podzielić kodem, który dziś napisałem na potrzeby mojego montażu (do trybu testowo-awaryjnego).

Jest to prosta, ale wszechstronna klasa odpalająca dowolną akcję w ściśle określonych interwałach.

Jeśli np. chcemy, żeby nasz kod wykonywał jeden krok silnika krokowego na 12345 mikrosekundy z dużą dokładnością (kilkanaście us), to jest w sam raz rozwiązanie.

Kod wygląda tak:

 

void setup() {
   Serial.begin(115200);
}

template <typename Callable>  // Callable to dowolny obiekt posiadający operator()
struct ConstantTimedCaller{
  ///////////////////////////////////////////
  ConstantTimedCaller(const unsigned long interval, Callable callable):
  // przekazujemy do konstruktora interwał i instancję obiektu (tworzy kopię!) żeby ją sobie wywoływać. 
    m_timeStart(micros()),
    m_timeAwaited(0ul),
    m_interval(interval),
    m_callable(callable)
  {
    SetAwaitedTime(0ul);
  }

  ///////////////////////////////////////////
  void SetAwaitedTime(const unsigned long leftover){
    m_timeAwaited = (unsigned long)(m_timeStart + m_interval - leftover);
  }
  
  ///////////////////////////////////////////
  // ta funkcja ogarnia odliczanie czasu i należy jej użyć w kodzie tam, gdzie chcemy coś realnie zrobić :)
  ///////////////////////////////////////////
  void CallWhenTime(){
    const unsigned long timeNow = micros();

    if (timeNow > m_timeAwaited){
      m_timeStart = timeNow;
      SetAwaitedTime((unsigned long)(timeNow - m_timeAwaited)); // ta różnica jest kluczowa pomiędzy kolejnymi wywołaniami
      m_callable(); // wywołaj operator() obiektu, który tutaj przekazałeś
    }
  }
  
  unsigned long m_timeStart;
  unsigned long m_timeAwaited;
  const unsigned long m_interval;
  Callable m_callable;
};

///////////////////////////////////////////
// Przykładowa funkcja do testów - wysyła na serial aktualny czas:
///////////////////////////////////////////
void printTime(){
  char lineBuffer[64] = {0};
  sprintf(lineBuffer, "Time is: %20lu", micros());
  Serial.println(lineBuffer);
}

typedef void (*voidFunctionPtr)(); 
// ten typedef jest po to, żeby powiedzieć template'owi jaki typ ma funkcja printTime (bo konstruktor się sam nie domyśli)
ConstantTimedCaller<voidFunctionPtr> serialPrintCaller(1234567ul, printTime);
  
///////////////////////////////////////////
// główna funkcja w Arduino
///////////////////////////////////////////
void loop(){
  serialPrintCaller.CallWhenTime(); // tak się tego używa :)
  
  // a tutaj poniżej już można sobie wywoływać cokolwiek chcemy. serialPrintCaller i tak wywoła się dokładnie o czasie!
  jakasFunkcja();
  costam();
  jakisInnyCallerZInnymInterwalem.CallWhenTime(); // też będzie działał zupełnie niezależnie!
}

 

 

Bardziej skomplikowany przykład użycia:

 

 

struct OnBoardLedLighter{
  OnBoardLedLighter():
    m_isLedOn(false)
  {}

  void operator()(){ // ta funkcja jest odpalana przez callera. Można tu robić cokolwiek!
    toggle();
  }
  
  void toggle(){ // mrugamy ledem...
    if (m_isLedOn){
      digitalWrite(LED_BUILTIN, LOW);
      m_isLedOn = false;
    }else{
      digitalWrite(LED_BUILTIN, HIGH);
      m_isLedOn = true;
    }
  }
  bool m_isLedOn;
};

OnBoardLedLighter onBoardLedLighter;
ConstantTimedCaller<OnBoardLedLighter> ledLighterCaller(900000ul, onBoardLedLighter);
  
void loop(){
    ledLighterCaller.CallWhenTime(); // będzie zapalać i gasić leda co 900 ms
}

 

Żeby sprawdzić, czy dobrze działa włączamy kod z pierwszego przykładu, uruchamiamy Serial Monitor i czytamy:

					różnica	
				
	Time is:	     2469172	1234572	
	Time is:	     3703744	1234576	
	Time is:	     4938320	1234544	
	Time is:	     6172864	1234572	
	Time is:	     7407436	1234572	
	Time is:	     8642008	1234576	
	Time is:	     9876584	1234572	
	Time is:	    11111156	1234552	
	Time is:	    12345708	1234592	
	Time is:	    13580300	1234548	
	Time is:	    14814848	1234556	
	Time is:	    16049404	1234580	
	Time is:	    17283984	1234552	
	Time is:	    18518536	1234580	
	Time is:	    19753116	1234556	
	Time is:	    20987672	1234584	
	Time is:	    22222256	1234560	
------------------------------------------------
Średnia:				1234567.29411765

 

Oczywiście, średnia to już policzone w excelu dla tych danych :)

Jak widać, już kilkanaście próbek daje dosyć fajną średnią zbliżoną do oczekiwanej 1234567 mikrosekund.

Dla 180 próbek średnia wyszła już: 1234567.09090909 :)

To znaczy, że nawet jeśli momentami trochę przyspiesza lub spowalnia (odchylenie standardowe ok.13 us) to zasadniczo w dłuższym okresie czasu jesteśmy naprawdę bardzo dobrze synchronizowani.

 

Nie jest to nic nowego ani odkrywczego, ale wydaje mi się, że to bardzo użyteczny kod, np. do sterowania krokowcem zgodnie z ruchem nieba...

Można mieć kilka takich callerów uruchomionych jednocześnie i będą doskonale współdziałać. To bardzo wygodne.

 

Pozdrawiam!

 

PS: będę badał zachowanie w okolicach 4294967295 mikrosekundy, bo wtedy przekręca się licznik unsigned long (ponad godzinę od uruchomienia). Raczej nie powinno się nic złego dziać, ale może "przeskoczyć" kilka kroków.

Edited by Behlur_Olderys
  • Like 2

Share this post


Link to post
Share on other sites

Nie jestem ekspertem od Arduino, ale aktywna pętla zwykle nie jest najlepszym rozwiązaniem. Nie lepiej użyć timerów i przerwań (rejestry TCCRxA i TCCRxB)? Rozdzielczość jest chyba taka sama jak zegara samego CPU.

  • Like 1

Share this post


Link to post
Share on other sites

arduino nie znam

mikrokontrolery programuję w C i tam używam timerów programowych o podobnej konstrukcji (z funkcjami zwrotnymi) w oparciu o timery sprzętowe

silniki krokowe obsługuję bezpośrednio w przerwaniach timera

 

pozdrawiam

Share this post


Link to post
Share on other sites

Niestety arduino i timery nie bardzo siebie lubią. Jakiś czas temu próbowałem zrobić sobie proste wysyłanie próbek z ADC z częstotliwością 1kHz i co się okazało, miałem bardzo zmienny bitrate, dodatkowo trzeba było szacowac ile taktów zegara zajmie dana funkcja i wliczyć w to opóźnienia. Do naprawdę szybkich i dokładnych akcji polecam STMy z DMA (direct memory access) - wtedy można zrobić prawdziwy system czasu rzeczywistego..

Share this post


Link to post
Share on other sites
1 hour ago, sidiouss said:

STMy z DMA

Schodzimy coraz niżej :)

 

Co do samego kodu, dość pokrętnie napisane, np:


m_timeStart = timeNow;
// leftover = timeNow - m_timeAwaited
// in SetAwaitedTime
m_timeAwaited = (unsigned long)(m_timeStart + m_interval - leftover);

// so
m_timeAwaited = m_interval + m_timeAwaited

Propozycja:

// constructor
nextCall = now() + interval;
delta = 0;

//active loop
unsigned long n = now();
if (nextCall - (delta >> 8) < n) {
  nextCall += interval;
  delta = ( (abs(nextCall - n) << 7) + (delta*4) )/5;  // old delta is weighted 4, new delta is weighted 1, this needs to be checked on hardware to determine right weights
  
  
  callWhatever();
}

W zaproponowanym rozwiązaniu:

- kolejne czasy liczę zawsze od bazowego (nextCall += interval), więc niedokładności wywołań nie wpływają na kolejne wywołania,

- kod jest odporny na przepełnienia, po prostu arytmetyka zadziała,

- gdyby stosować proste if (nextCall < n to czasy odchylały by się zawsze w jedną stronę, było by spóźnienie, stąd delta która odchyla to w stronę przeciwną

- delta za każdym razem się koryguje (średnia ważona, waga 4 na starą wartość, waga 1 na nową)

- delta ma 8 bitów dokładności po przecinku, nowa jest przesuwana o 7 bitów bo interesuje nas połowa delty żeby rozłożyć czasu równomiernie po obu stronach

- jeśli na Arduino mnożenie i dzielenie jest drogie (nie wiem tego) to *4 można zamienić na << 2

 

Nie kompilowałem/debuggowałem/testowałem tego kodu, więc ręki nie dam sobie uciąć, że wszystko działa poprawnie - ale pokazuje o co chodzi.

 

EDIT: ta delta może być przedwczesną optymalizacją - bez tego pewnie średnio zadziała tak samo dobrze. 

 

Edited by Sebo_b
  • Like 1

Share this post


Link to post
Share on other sites
Godzinę temu, sidiouss napisał:

Niestety arduino i timery nie bardzo siebie lubią. Jakiś czas temu próbowałem zrobić sobie proste wysyłanie próbek z ADC z częstotliwością 1kHz i co się okazało, miałem bardzo zmienny bitrate, dodatkowo trzeba było szacowac ile taktów zegara zajmie dana funkcja i wliczyć w to opóźnienia. Do naprawdę szybkich i dokładnych akcji polecam STMy z DMA (direct memory access) - wtedy można zrobić prawdziwy system czasu rzeczywistego..

problemy z wysyłką przez UART?

to wina błędnego wyliczenia preskalera. Najprościej zastosować kwarc "przyjazny" dla seriala i wszystkie problemy znikną

 

pozdrawiam

  • Like 1

Share this post


Link to post
Share on other sites
1 hour ago, sidiouss said:

1kHz

1kHz? Rozumiem, że to 1 kbps. 20 lat temu na 8051 wyciągałem stabilne 115200bps (choć nie na timerach). Więc 1kHz dzisiaj to nie powinien być żaden problem. 

Share this post


Link to post
Share on other sites
12 minut temu, ZbyT napisał:

problemy z wysyłką przez UART?

to wina błędnego wyliczenia preskalera. Najprościej zastosować kwarc "przyjazny" dla seriala i wszystkie problemy znikną

 

pozdrawiam

 

1 minutę temu, Sebo_b napisał:

1kHz? Rozumiem, że to 1 kbps. 20 lat temu na 8051 wyciągałem stabilne 115200bps (choć nie na timerach). Więc 1kHz dzisiaj to nie powinien być żaden problem. 

Panowie, nie prędkość transmisji, bo to jest oczywiste, że najprostszy mikrokontroler będzie w stanie przesłać nawet 1Mbs bez zakłóceń, mi chodziło o próbkowanie ADC 1kHz i przesłanie tego w czasie rzeczywistym, czyli zarejestrować 1000 próbek w ciągu sekundy i potem je przesłać :)

Arduino na 8Mhz nie wyrabia się, bo samo sobie wprowadza opóźnienia związane z próbkowaniem ADC i przesyłaniem przez UART. Gdy zaprogramuje się Arduino jako zwykłego AVRa wykorzystując sam język C, to problem nie występuje. Coś za coś - w Arduino mamy prosty język programowania kosztem niektórych funkcjonalności. W STMie przy próbkowaniu 12 kanałów ADC (multipleksowaniem) i wysyłaniem UARTem przez DMA udało mi się stabilne uzyskać 3000 próbek na sekundę z wysyłaniem 1Mbps :) Ale już nie offtopuję ;)

  • Like 3

Share this post


Link to post
Share on other sites
Godzinę temu, Sebo_b napisał:

Schodzimy coraz niżej :)

 

Co do samego kodu, dość pokrętnie napisane, np:



m_timeStart = timeNow;
// leftover = timeNow - m_timeAwaited
// in SetAwaitedTime
m_timeAwaited = (unsigned long)(m_timeStart + m_interval - leftover);

// so
m_timeAwaited = m_interval + m_timeAwaited

Propozycja:


// constructor
nextCall = now() + interval;
delta = 0;

//active loop
unsigned long n = now();
if (nextCall - (delta >> 8) < n) {
  nextCall += interval;
  delta = ( (abs(nextCall - n) << 7) + (delta*4) )/5;  // old delta is weighted 4, new delta is weighted 1, this needs to be checked on hardware to determine right weights
  
  
  callWhatever();
}

W zaproponowanym rozwiązaniu:

- kolejne czasy liczę zawsze od bazowego (nextCall += interval), więc niedokładności wywołań nie wpływają na kolejne wywołania,

- kod jest odporny na przepełnienia, po prostu arytmetyka zadziała,

- gdyby stosować proste if (nextCall < n to czasy odchylały by się zawsze w jedną stronę, było by spóźnienie, stąd delta która odchyla to w stronę przeciwną

- delta za każdym razem się koryguje (średnia ważona, waga 4 na starą wartość, waga 1 na nową)

- delta ma 8 bitów dokładności po przecinku, nowa jest przesuwana o 7 bitów bo interesuje nas połowa delty żeby rozłożyć czasu równomiernie po obu stronach

- jeśli na Arduino mnożenie i dzielenie jest drogie (nie wiem tego) to *4 można zamienić na << 2

 

Nie kompilowałem/debuggowałem/testowałem tego kodu, więc ręki nie dam sobie uciąć, że wszystko działa poprawnie - ale pokazuje o co chodzi.

 

EDIT: ta delta może być przedwczesną optymalizacją - bez tego pewnie średnio zadziała tak samo dobrze. 

 

 

Ciekawe, chyba masz rację z tym timeAwaited, ciekawe co się stanie jak tak napiszę :) Może w weekend znajdę trochę czasu...

Ale operatory przesunięcia w Twoim kodzie jakoś mnie zniechęcają strasznie :)

Share this post


Link to post
Share on other sites
1 hour ago, Behlur_Olderys said:

 

Ciekawe, chyba masz rację z tym timeAwaited, ciekawe co się stanie jak tak napiszę :) Może w weekend znajdę trochę czasu...

Ale operatory przesunięcia w Twoim kodzie jakoś mnie zniechęcają strasznie :)

Może za mało embedded kodu widziałeś :) Można to owrapować w define'y - ale moim zdaniem wtedy nie jest czytelne. Jak usuniesz delta, bo myślę, że z nią to przesadziłem (zrób testy na sprzęcie, sam jestem ciekaw) - to kod się uprasza do kilku linii i imo jest mega czytelny.

 

Oczywiście założenie jest, że chcesz mieć jak najdokładniej zachowany czas całej sekwencji, a nie różnicę pomiędzy wywołaniami.

 

// constructor
nextCall = now() + interval;

//active loop
if ( nextCall < now() ) {
  nextCall += interval;
  callWhatever();
}

 

  • Like 1

Share this post


Link to post
Share on other sites
59 minut temu, Sebo_b napisał:

Może za mało embedded kodu widziałeś :) Można to owrapować w define'y - ale moim zdaniem wtedy nie jest czytelne. Jak usuniesz delta, bo myślę, że z nią to przesadziłem (zrób testy na sprzęcie, sam jestem ciekaw) - to kod się uprasza do kilku linii i imo jest mega czytelny.

 

Oczywiście założenie jest, że chcesz mieć jak najdokładniej zachowany czas całej sekwencji, a nie różnicę pomiędzy wywołaniami.

 


// constructor
nextCall = now() + interval;

//active loop
if ( nextCall < now() ) {
  nextCall += interval;
  callWhatever();
}

 

Może powinienem się zacząć uczyć więcej embedded, jest z tym dużo pracy dla programistów ostatnio :)

 

Share this post


Link to post
Share on other sites

Na poczatku swojej przygody z Arduino napisalem cos takiego. Przygotowane z mysla o interwalometrze dla lustrzanki. Zadania typu "wykonaj 10 zdjec z interwalem 2s, poczekaj 12s przed startem".

 

Uzycie:
- co 2000 ms wykonaj funkcje blinkLED

timer.addEverytime(2000, blinkLED);


- wykonaj dwa razy funkcje itWorks co 2000 ms, poczekaj 300ms przed pierwszym wykonaniem
timer.addTask(300, 2, 2000, itWorks);

class RZTimer
{
  public:
    size_t tasks, tasksET;
    typedef void( * makeThisT )();

    typedef struct {
      unsigned long time;
      unsigned int interval, iterations, iterationsDone;
      makeThisT makeThis;
    } parameter;
    parameter *parameters;

    typedef struct {
      unsigned long timeET;
      unsigned int intervalET;
      makeThisT makeThisET;
    } parameterET;
    parameterET *parametersET;

    void run();
    size_t addTask(unsigned int, unsigned int, unsigned int, void (*makeT)());
    void removeTask(size_t);
    size_t addEverytime(unsigned int, void (*makeT)());
    void removeEverytime(size_t);
    size_t findTaskID(void (*makeT)());
    size_t findTaskIDEverytime(void (*makeT)());
};

size_t RZTimer::addTask(unsigned int _wait, unsigned int _iterations, unsigned int _interval, void (*makeT)())
{
	tasks++;
	parameters = (parameter*) realloc(parameters, tasks * sizeof(parameter));
	parameters[tasks - 1].time = millis() + _wait;
	parameters[tasks - 1].interval = _interval;
	parameters[tasks - 1].iterations = _iterations;
	parameters[tasks - 1].iterationsDone = 0;
	parameters[tasks - 1].makeThis = makeT;
	
	if (parameters[tasks - 1].iterations == 0)
	{
		parameters[tasks - 1].iterations = 1;
	}
	return tasks - 1;
}

size_t RZTimer::addEverytime(unsigned int _interval, void (*makeT)())
{
	tasksET++;
	parametersET = (parameterET*) realloc(parametersET, tasksET * sizeof(parameterET));
	parametersET[tasksET - 1].timeET = millis();
	parametersET[tasksET - 1].intervalET = _interval;
	parametersET[tasksET - 1].makeThisET = makeT;
	
	//makeT();
	return tasksET - 1;
}

void RZTimer::run()
{
	for (size_t i = 0; i < tasks; i++) {
		if (parameters[i].iterations)
		{
			if (static_cast<long>(millis() - parameters[i].time) >= 0)
			{
				parameters[i].time += parameters[i].interval;
				parameters[i].makeThis();
				parameters[i].iterationsDone = parameters[i].iterationsDone + 1;
				if (parameters[i].iterationsDone >= parameters[i].iterations)
				{
					removeTask(i);
				}
			}
		}
	}
	
	for (size_t i = 0; i < tasksET; i++) {
		if (static_cast<long>(millis() - parametersET[i].timeET) >= 0)
		{
			parametersET[i].timeET += parametersET[i].intervalET;
			parametersET[i].makeThisET();
		}
	}
}

void RZTimer::removeTask(size_t _task)
{
	if (_task < tasks) {
		for (size_t i = _task; i < tasks - 1; i++) {
			parameters[i] = parameters[i + 1];
		}
		tasks--;
		parameters = (parameter*) realloc(parameters, tasks * sizeof(parameter));
	}
}

void RZTimer::removeEverytime(size_t _task)
{
	if (_task < tasksET) {
		for (size_t i = _task; i < tasksET - 1; i++) {
			parametersET[i] = parametersET[i + 1];
		}
		tasksET--;
		parametersET = (parameterET*) realloc(parametersET, tasksET * sizeof(parameterET));
	}
}

size_t RZTimer::findTaskID(void (*makeT)())
{
	for (size_t i = 0; i < tasks; i++) {
		if (parameters[i].makeThis == makeT) {
			return i;
		}
	}
	return (size_t) ~0;   
}

size_t RZTimer::findTaskIDEverytime(void (*makeT)())
{
	for (size_t i = 0; i < tasksET; i++) {
		if (parametersET[i].makeThisET == makeT) {
			return i;
		}
	}
	return (size_t) ~0;   
}

RZTimer timer;
void blinkLED();

void setup() {
  Serial.begin(9600);
  pinMode(LED_BUILTIN, OUTPUT);
  timer.addEverytime(2000, blinkLED);
  auto itWorks = []()->void {Serial.print("It Works! "); Serial.print(millis()); Serial.println(" ms");};
  timer.addTask(300, 2, 2000, itWorks);
}

void loop() {
  timer.run();
}

void blinkLED()
{
  Serial.print("ON ");
  Serial.print(millis());
  Serial.println(" ms");
  digitalWrite(LED_BUILTIN, HIGH);
  auto blinkLEDoff = []()->void {digitalWrite(LED_BUILTIN, LOW); Serial.print("OFF "); Serial.print(millis()); Serial.println(" ms");};
  timer.addTask(1000, 1, 0, blinkLEDoff);
}

 

Edited by r.ziomber

Share this post


Link to post
Share on other sites
9 hours ago, Behlur_Olderys said:

jest z tym dużo pracy dla programistów ostatnio

Wciąż jest dużo, ale już coraz mniej. Oprócz pracy typowo dla elektronika: komórki od czasu iOSa i Androida to już nie embedded. Zostały telewizory, ale tam też coraz więcej Androida, więc jeśli nie pracujesz u SoC vendora (np MTK) to też nie embedded. Automotive się jeszcze broni rękami i nogami przed Googlem i Applem.

  • Like 1

Share this post


Link to post
Share on other sites
W dniu 23.08.2018 o 13:16, Sebo_b napisał:

Może za mało embedded kodu widziałeś :) Można to owrapować w define'y - ale moim zdaniem wtedy nie jest czytelne. Jak usuniesz delta, bo myślę, że z nią to przesadziłem (zrób testy na sprzęcie, sam jestem ciekaw) - to kod się uprasza do kilku linii i imo jest mega czytelny.

 

Oczywiście założenie jest, że chcesz mieć jak najdokładniej zachowany czas całej sekwencji, a nie różnicę pomiędzy wywołaniami.

 


// constructor
nextCall = now() + interval;

//active loop
if ( nextCall < now() ) {
  nextCall += interval;
  callWhatever();
}

 

Miałeś rację, działa dokładnie tak samo! :) W sumie to prosta kalkulacja, trudno żeby matematyka się myliła, a jednak byłem trochę zaskoczony :)

Kod jeszcze bardziej się upraszcza, a średni interwał pomiędzy wywołaniami i tak się zgadza co do ułamka mikrosekundy - super! (właśnie sprawdziłem na żywo)

 

Edited by Behlur_Olderys
  • Like 1

Share this post


Link to post
Share on other sites
W dniu 23.08.2018 o 23:32, r.ziomber napisał:

Na poczatku swojej przygody z Arduino napisalem cos takiego. Przygotowane z mysla o interwalometrze dla lustrzanki. Zadania typu "wykonaj 10 zdjec z interwalem 2s, poczekaj 12s przed startem".

 

Uzycie:
- co 2000 ms wykonaj funkcje blinkLED

timer.addEverytime(2000, blinkLED);


- wykonaj dwa razy funkcje itWorks co 2000 ms, poczekaj 300ms przed pierwszym wykonaniem
timer.addTask(300, 2, 2000, itWorks);


class Timer
{
  public:
    unsigned long *time, *timeET;
    unsigned int *interval, *intervalET, *iterations, *iterationsDone;
    int tasks, tasksET;
    typedef void( * makeThisT )();
    makeThisT *makeThis;
    makeThisT *makeThisET;
    void run();
    int addTask(unsigned int, unsigned int, unsigned int, void (*makeT)());
    void removeTask(int);
    int addEverytime(unsigned int, void (*makeT)());
    void removeEverytime(int);
};
void blinkLED();

Timer timer;

void setup() {
  Serial.begin(9600);
  pinMode(LED_BUILTIN, OUTPUT);
  timer.addEverytime(2000, blinkLED);
  auto itWorks = []()->void {Serial.print("It Works! "); Serial.println(millis());};
  timer.addTask(300, 2, 2000, itWorks);
}

void loop() {
  timer.run();
}

int Timer::addTask(unsigned int w, unsigned int it, unsigned int in, void (*makeT)())
{
  tasks++;
  time = (unsigned long*) realloc(time, tasks * sizeof(unsigned long));
  time[tasks - 1] = millis() + w;
  interval = (unsigned int*) realloc(interval, tasks * sizeof(unsigned int));
  interval[tasks - 1] = in;
  iterations = (unsigned int*) realloc(iterations, tasks * sizeof(unsigned int));
  iterations[tasks - 1] = it;
  iterationsDone = (unsigned int*) realloc(iterationsDone, tasks * sizeof(unsigned int));
  iterationsDone[tasks - 1] = 0;
  makeThis = (makeThisT*) realloc(makeThis, tasks * sizeof(makeThisT));
  makeThis[tasks - 1] = makeT;
  if (iterations[tasks - 1] == 0)
  {
    iterations[tasks - 1] = 1;
  }
  return tasks - 1;
}

int Timer::addEverytime(unsigned int in, void (*makeT)())
{
  tasksET++;
  timeET = (unsigned long*) realloc(timeET, tasksET * sizeof(unsigned long));
  timeET[tasksET - 1] = millis();
  intervalET = (unsigned int*) realloc(intervalET, tasksET * sizeof(unsigned int));
  intervalET[tasksET - 1] = in;
  makeThisET = (makeThisT*) realloc(makeThisET, tasksET * sizeof(makeThisT));
  makeThisET[tasksET - 1] = makeT;
  //makeT();
  return tasksET - 1;
}

void Timer::run()
{
  for (int i = 0; i < tasks; i++) {
    if (iterations[i])
    {
      if ((millis() >= time[i]))
      {
        time[i] += interval[i];
        makeThis[i]();
        iterationsDone[i] = iterationsDone[i] + 1;
        if (iterationsDone[i] >= iterations[i])
        {
          removeTask(i);
        }
      }
    }
  }

  for (int i = 0; i < tasksET; i++) {
    if ((millis() >= timeET[i]))
    {
      timeET[i] += intervalET[i];
      makeThisET[i]();
    }
  }
}

void Timer::removeTask(int _task)
{
  if (_task < tasks) {
    for (int i = _task; i < tasks - 1; i++) {
      time[i] = time[i + 1];
      interval[i] = interval[i + 1];
      iterations[i] = iterations[i + 1];
      iterationsDone[i] = iterationsDone[i + 1];
      makeThis[i] = makeThis[i + 1];
    }
    tasks--;
    time = (unsigned long*) realloc(time, tasks * sizeof(unsigned long));
    interval = (unsigned int*) realloc(interval, tasks * sizeof(unsigned int));
    iterations = (unsigned int*) realloc(iterations, tasks * sizeof(unsigned int));
    iterationsDone = (unsigned int*) realloc(iterationsDone, tasks * sizeof(unsigned int));
    makeThis = (makeThisT*) realloc(makeThis, tasks * sizeof(makeThisT));
  }
}

void Timer::removeEverytime(int _task)
{
  if (_task < tasksET) {
    for (int i = _task; i < tasksET - 1; i++) {
      timeET[i] = timeET[i + 1];
      intervalET[i] = intervalET[i + 1];
      makeThisET[i] = makeThisET[i + 1];
    }
    tasksET--;
    timeET = (unsigned long*) realloc(timeET, tasksET * sizeof(unsigned long));
    intervalET = (unsigned int*) realloc(intervalET, tasksET * sizeof(unsigned int));
    makeThisET = (makeThisT*) realloc(makeThisET, tasksET * sizeof(makeThisT));
  }
}

void blinkLED()
{
  Serial.print("ON ");
  Serial.println(millis());
  digitalWrite(LED_BUILTIN, HIGH);
  auto blinkLEDoff = []()->void {digitalWrite(LED_BUILTIN, LOW); Serial.print("OFF "); Serial.println(millis());};
  timer.addTask(1000, 1, 0, blinkLEDoff);
}

 

 

Nie mogę powstrzymać się od komentarza - zboczenie zawodowe:

To dziwne, że można używać auto i lambdy z C++11, a nie można używać std::vectora, który mocno ułatwiłby sprawę :)

Przez to, że masz "managera" tasków, który musi pilnować wskaźników, indeksów i pamięci Twój kod jest bardzo skomplikowany, a przecież z praktycznego punktu widzenia trzymanie wszystkich tasków razem w jednym obiekcie pamięci nie daje jakichś bezpośrednich korzyści, może poza tym, że jeśli task "wygaśnie" to go usuwasz i nie przetwarzasz niepotrzebnie (choć to marginalny zysk kilku cykli procesora na ewaluację 'ifa'). W moim rozwiązaniu każdy "task" to osobny obiekt, przez co nie muszę się martwić pamięcią (jest alokowana na stosie) ani indeksami (nie ma żadnych tablic).

Zasadniczo rozróżnianie tasków "zwykłych" i ET wydaje się również niepotrzebne - no, chyba że oszczędność pamięci.

Oczywiście te uwagi są pisane z perspektywy kogoś, kto na co dzień dysponuje 48-rdzeniowymi procesorami z setkami GB RAM-u, dlatego pewnie właśnie taka jest różnica między kodem "zwykłym" a embedded. Dlatego proszę,  nie traktuj tego jako krytykę, a raczej jako analizę porównawczą :)

 

 

PS

Przepraszam za dwa posty, ale to dwie różne sprawy :) Może moderator jakiś połączy te dwa posty jeśli tak lepiej...

  • Like 1

Share this post


Link to post
Share on other sites

Ale Arduino bez dodatkowych bibliotek nie obsluguje std::vector (w ogole STD) i stad takie mieszanie C++ z C.

https://github.com/maniacbug/StandardCplusplus

6 godzin temu, Behlur_Olderys napisał:

Zasadniczo rozróżnianie tasków "zwykłych" i ET wydaje się również niepotrzebne - no, chyba że oszczędność pamięci.

Zadanie "doczesne" przechowuje jedynie informacje o interwale. Wykonywane kilka razy dodatkowo musi miec informacje o ilosci cykli i "poczekalni" przed pierwszym wykonaniem.

 

6 godzin temu, Behlur_Olderys napisał:

Dlatego proszę,  nie traktuj tego jako krytykę, a raczej jako analizę porównawczą

Ale ja oczekuje krytyki! Chce sie uczyc od zawodowych programistow i doskonalic swoje znikome umiejetnosci.

Jesli tylko masz czas napisac, z checia wyslucham, co zrobilem zle!

 

Mam jeszcze glupi pomysl, o ktory byc moze rozbuduje swoj timer. Terminarz oparty na... delay(). No dobra, nie na delay(), a na uspijMikrokontroler(), ale w zewnetrznym zachowaniu to prawie to samo :) Po prostu znajde najblizsze zadanie w tabelicach time i timeET i do tego czasu uspie mikrokontroler. Bedzie to dodatkowa opcja, bo oczywiscie nienadajaca sie do kazdego projektu.

PS. Ktos kiedys powiedzial, ze na delay() nie da sie zrobic wielozadaniowosci. Dlatego tez chce ja zrobic :P

Edited by r.ziomber

Share this post


Link to post
Share on other sites

Czy moge prosic o weryfikacje? Zamiast

W dniu 23.08.2018 o 13:16, Sebo_b napisał:

 


// constructor
nextCall = now() + interval;

//active loop
if ( nextCall < now() ) {
  nextCall += interval;
  callWhatever();
}

proponuje

#include <limits.h>

// constructor
nextCall = now() + interval;

//active loop
if ( nextCall < now() && now() - nextCall < ULONG_MAX / 2 ) {
  nextCall += interval;
  callWhatever();
}

Powod: przedzial czasu obejmujacy przekroczenie zakresu zmiennej. now() bedzie wtedy olbrzymie a nextCall male. Warunek nextCall < now() zostanie wiec spelniony.

Standardowe rozwiazanie https://www.arduino.cc/en/Tutorial/BlinkWithoutDelay nie sprawia klopotow z overflow, ale kumuluje opoznienie kolejnych krokow.

Edited by r.ziomber
  • Like 1

Share this post


Link to post
Share on other sites
4 hours ago, r.ziomber said:

przedzial czasu obejmujacy przekroczenie zakresu zmiennej

Masz rację, że jest tam bug, a nawet dwa:

1. Overflow nextCall - tak jak napisałeś, now() będzie duże, nextCall małe

2. Overflow now() bo nie mamy pewności czy pod koniec zakresu zdąży się wykonać, czyli nextCall będzie duże, a now() małe

 

To są dodatkowe cornercase'y, więc || a nie && w Twoim rozwiązaniu powinien to załatwić, tylko że pierwszy warunek zawiera się w tym co zaproponowałeś - więc wystarczyło by :

if ( now() - nextCall < ULONG_MAX / 2 ) {

// lub - bez dzielenia (choć to tylko estetyka w tym przypadku)
                                 
if ( now() - nextCall < (ULONG_MAX >> 1) ) {

// lub - moim zdaniem bardziej czytelnie
                                 
if ( now() - nextCall < LONG_MAX )
  
// lub - bez użycia limits
  
if ( now() - nextCall < (~0ul >> 1) )
  
// a ja bym napisał to tak, żeby było dla każdego czytelne (choć nie widać tak od razu, że obsługuje corner-case'y)
  
if ( static_cast<long>(now() - nextCall) >= 0 ) {

 

Edited by Sebo_b
  • Like 2

Share this post


Link to post
Share on other sites
3 godziny temu, r.ziomber napisał:

Czy moge prosic o weryfikacje? Zamiast

proponuje


#include <limits.h>

// constructor
nextCall = now() + interval;

//active loop
if ( nextCall < now() && now() - nextCall < ULONG_MAX / 2 ) {
  nextCall += interval;
  callWhatever();
}

Powod: przedzial czasu obejmujacy przekroczenie zakresu zmiennej. now() bedzie wtedy olbrzymie a nextCall male. Warunek nextCall < now() zostanie wiec spelniony.

Standardowe rozwiazanie https://www.arduino.cc/en/Tutorial/BlinkWithoutDelay nie sprawia klopotow z overflow, ale kumuluje opoznienie kolejnych krokow.

 

 

Sytuacja jest taka:

now = 1022

interval = 10

timeToWait = 1021

 

1. Start

2. Warunek spełniony 

3. timeToWait := 7 (1031mod 1024)

4. doSomething(now==1022)

5. Powiedzmy, że mija 1ms (now:=1023)

6. Znowu wchodzimy do warunku

7. Warunek spełniony (7 < 1023)

8. timeToWait = 17

9. doSomething (now==1023)

10. Mija znowu 1ms (now:=0)

11. timeToWait > now()

Następne doSomething odpali się za 17ms. Będziemy mieli 3 doSomething w czasie 20ms. Czyli średnio wychodzi ok :) Więc overflow timeToWait to nic groźnego na dłuższą skalę. 

 

Overflow now() jest dużo gorszy, bo timeToWait będzie np. 1023 a now() przekroczy już 0 i stracimy wszystkie wywołania!

 

Napisałbym testy do tych funkcji i sprawdził wszystkie case'y z automatu :)

 

static_cast wygląda najbardziej elegancko, ale znowu - muszę sam się przekonać :)

  • Like 1

Share this post


Link to post
Share on other sites

Z tym co zaproponowane overflowy nie powinny mieć wpływu na średnie czasy - arytmetyka zadziała.

 

1 minute ago, Behlur_Olderys said:

static_cast wygląda najbardziej elegancko, ale znowu - muszę sam się przekonać :)

Tutaj nie ma się co przekonywać, to trochę jak byś przyjechał z Anglii (pisanie w C) i twierdził, że musisz się przekonać do prawostronnego ruchu (C++) :) Piszesz w C++ to używać "pożądnych" castów. Co więcej, zaczniesz (lepiej nie) używać wielodziedziczenia i C casty się wysypią.

Share this post


Link to post
Share on other sites

(post do usunięcia)

Edited by Sebo_b

Share this post


Link to post
Share on other sites
12 hours ago, r.ziomber said:

Ale ja oczekuje krytyki! Chce sie uczyc od zawodowych programistow i doskonalic swoje znikome umiejetnosci.

Jesli tylko masz czas napisac, z checia wyslucham, co zrobilem zle!

Na szybko zrefaktorowałem Twój kod, a w sumie to napisałem go od nowa zachowując logikę. Nie testowałem tego kodu (tylko syntax), więc jest pewnie sporo bugów. Ale wyłapanie bugów to dobre ćwiczenie :)

 

#include <limits.h>
#include <cstdlib>

class Timer {

public:

	typedef void(*Callback)(void*);

	Timer();
	~Timer();
	/**
	 * count < 0 for unlimited execution
	 * returns task number or 0 if fail;
	 **/
	size_t addTask(long delay, long interval, int count, Callback callback, void* cbParam);
	void removeTask(size_t taskId);

	void run();

	void cleanUp();

private:

	struct Task {
		long interval;
		unsigned long nextCall;
		int count;
		size_t taskId;

		Callback cb;
		void* cbParam;
	};

	Task* tasks;
	size_t tasksSize;
	size_t taskIdCounter;
	bool clean;
	unsigned long nextCall;

};

Timer::Timer():
	tasks(NULL), tasksSize(0), clean(true), nextCall(ULONG_MAX), taskIdCounter(0)
{
}

Timer::~Timer() {
	if (tasks)
		free(tasks);
}

size_t Timer::addTask(long delay, long interval, int count, Timer::Callback callback, void* cbParam) {

	if (count == 0 || callback == NULL || interval == 0)
		return 0;

	Task *t = NULL;
	unsigned long now = ::now();

	if (!clean) {
		for (size_t i = 0; i < tasksSize; ++i) {
			if (tasks[i].count == 0) {
				if (t == NULL) {
					t = tasks+i;
					clean = true;					
				}
				else {
					clean = false;
				}
			}
		}
	}
	else {

		Task* newTasks = static_cast<Task*>(realloc(tasks,(tasksSize+1)*sizeof(Task)));
		if (!newTasks)
			return 0;
		tasks = newTasks;
		t = newTasks + tasksSize++;
	}

	t->interval = interval;
	t->nextCall = now + interval + delay;
	t->count = count;
	t->cb = callback;
	t->cbParam = cbParam;
	t->taskId = ++taskIdCounter;

	if ( static_cast<long>(nextCall - t->nextCall) >= 0)
		nextCall = t->nextCall;

	return t->taskId;
}

void Timer::removeTask(size_t taskId) {

// we don't need to update nextCall, in the worst case we end up with one empty loop in run()
//	unsigned long nextCallSoFar = ULONG_MAX;

	for (size_t i = 0; i < tasksSize; ++i) {
		if (tasks[i].taskId == taskId) {
			tasks[i].count = 0;
			clean = false;

//			if ( nextCall != tasks[i].nextCall)
				return;
		}
//		else if (static_cast<long>(nextCallSoFar - tasks[i].nextCall) >= 0) {
//			nextCallSoFar = tasks[i].nextCall;
//		}
	}

//	nextCall = nextCallSoFar;
}

void Timer::run() {

	unsigned long now = ::now();

	if ( static_cast<long>(now - nextCall) >= 0 ) {

		for (size_t i = 0; i < tasksSize; ++i) {
			
			nextCall = ULONG_MAX;

			if (tasks[i].count != 0 && static_cast<long>(now - tasks[i].nextCall) >= 0) {

				tasks[i].cb( tasks[i].cbParam);

				if (tasks[i].count > 0 && --tasks[i].count == 0)
					clean = false;
				else 
					tasks[i].nextCall = now + tasks[i].interval;
			}

			if ( static_cast<long>(nextCall - tasks[i].nextCall) >= 0)
				nextCall = tasks[i].nextCall;
		}
	}

}

void Timer::cleanUp() {

	if (clean)
		return;

	Timer *head = tasks, *tail = tasks + tasksSize - 1;

	while (head < tail) {

		if (head->count == 0 && tail->count != 0) {
			*head  = *tail;
			tail->count = 0;
		}

		if (head->count != 0)
			++head;
		if (tail->count == 0)
			--tail;
	}

	size_t newTasksSize = head-tasks;
	Task* newTasks = static_cast<Task*>(realloc(tasks,newTasksSize*sizeof(Task)));
	if (newTasks == NULL)
		return;	// we basically keep it unclan

	tasks = newTasks;
	tasksSize = newTasksSize;
	clean = true;
}

 

Edited by Sebo_b
  • Like 1

Share this post


Link to post
Share on other sites
1 godzinę temu, Sebo_b napisał:

Z tym co zaproponowane overflowy nie powinny mieć wpływu na średnie czasy - arytmetyka zadziała.

 

Tutaj nie ma się co przekonywać, to trochę jak byś przyjechał z Anglii (pisanie w C) i twierdził, że musisz się przekonać do prawostronnego ruchu (C++) :) Piszesz w C++ to używać "pożądnych" castów. Co więcej, zaczniesz (lepiej nie) używać wielodziedziczenia i C casty się wysypią.

Chodzi mi o sprawdzenie, czy corner case'y będą dobrze pokryte, bo wydaje się że tak, ale ja już dawno przestałem ufać swojemu rozumieniu czytanego kodu, i muszę mieć wszystko przetestowane :) static_cast to wiadomo, że elegancka konstrukcja.

 

24 minuty temu, Sebo_b napisał:

Na szybko zrefaktorowałem Twój kod

Jakoś nie podoba mi się stosowanie raz long, raz unsigned long.


Ogólnie - gdyby zaimplementować z boku gdzieś najprostszą protezę std::vector to mój "idealny" kod wyglądałby tak: (pseudo kod raczej)

 

struct Task{
   virtual bool IsValid(){ return true; }
   virtual void PerformIfReady();
}

struct TaskWithCount : Task;

vector<Task> tasks;
tasks.emplace_back(Task(1));
tasks.emplace_back(TaskWithCount(1, 10));

for (task : tasks){
    if (!task.IsValid()){
       tasks.erase(task);
    }else{
      task.PerformIfReady();
    }
}

Oczywiście PerformIfReady trzeba by zaimplementować odpowiednio, tak jak sugerujecie, ale chodzi mi tylko o ogólną architekturę.

 

 

 

PS

Zrobił się wątek programistyczny na forum astro :) 

 

 

  • Like 1

Share this post


Link to post
Share on other sites
1 minute ago, Behlur_Olderys said:

Jakoś nie podoba mi się stosowanie raz long, raz unsigned long.

Nie rozumiem, w którym miejscu?

 

2 minutes ago, Behlur_Olderys said:

gdzieś najprostszą protezę std::vector

To jest oczywiście kolejny krok, wydzielenie kolekcji na zewnątrz. Ale z drugiej strony jest to taka prosta struktura, że można pominąć.

 

 

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


  • Recently Browsing   0 members

    No registered users viewing this page.

  • Our picks

    • Zdjęcie Czarnej Dziury - dzisiaj o 15:00
      Pamiętajcie, że dzisiaj o 15:00 poznamy obraz Czarnej Dziury. Niezależnie od tego, jak bardzo będzie ono spektakularne (lub wręcz przeciwnie - parę pikseli), trzeba pamiętać, że to ogromne, wręcz niewyobrażalne, osiągnięcie cywilizacji. Utrwalić coś tak odległego i małego kątowo, do tego wykorzystując mega sprytny sposób (interferometria radiowa), ...no po prostu niewyobrażalne. EHT to przecież wirtualny teleskop wielkości planety. Proste?
        • Love
        • Like
      • 144 replies
    • Amatorska spektroskopia supernowych - ważne obserwacje klasyfikacyjne
      Poszukiwania i obserwacje supernowych w innych galaktykach zajmuje wielu astronomów, w tym niemałą grupę amatorów (może nie w naszym kraju, ale mam nadzieję, że pomału będzie nas przybywać). Odkrycie to oczywiście pierwszy etap, ale nie mniej ważne są kolejne - obserwacje fotometryczne i spektroskopowe.
        • Like
      • 4 replies
    • Odszedł od nas Janusz Płeszka
      Wydaje się nierealne, ale z kilku źródeł informacja ta zdaje się być potwierdzona. Odszedł od nas człowiek, któremu polskiej astronomii amatorskiej możemy zawdzięczyć tak wiele... W naszym hobby każdy przynajmniej raz miał z nim styczność. Janusz Płeszka zmarł w wieku 52 lat.
        • Sad
      • 167 replies
    • Małe porównanie mgławic planetarnych
      Postanowiłem zrobić taki kolaż będący podsumowaniem moich tegorocznych zmagań z mgławicami planetarnymi a jednocześnie pokazujący różnice w wielkości kątowe tych obiektów.
      Wszystkie mgławice na tej składance prezentowałem i opisywałem w formie odrębnych tematów na forum więc nie będę się rozpisywał o każdym obiekcie z osobna - jak ktoś jest zainteresowany szczegółami bez problemu znajdzie fotkę danej mgławicy na forum.
        • Love
        • Thanks
        • Like
      • 29 replies
    • SN 2018hhn - "polska" supernowa w UGC 12222
      Dziś mam przyjemność poinformować, że jest już potwierdzenie - obserwacja spektroskopowa wykonana na 2-metrowym Liverpool Telescope (La Palma, Wyspy Kanaryjskie). Okazuje się, że mamy do czynienia z supernową typu Ia. Poniżej widmo SN 2018hhn z charakterystyczną, silną linią absorpcyjną SiII.
        • Thanks
        • Like
      • 11 replies
×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.