Skocz do zawartości

Dlaczego "millis jest złe" ;)


SOYER

Pomocna odpowiedź

Pisząc na STM czy AVR w AS bez problemu zmienisz systick na 64 bity, w Arduino nie, chyba, ze znasz sposób na zmianę millis? Ja nie znam. Można oczywiście napisać własną alternatywę ale co z funkcjami w bibliotekach, które używają millis? Trzeba je zmodyfikować ale to zadanie przekracza możliwości przeciętnego użytkownika Arduino. Nawet jak zmodyfikujesz, to po wgraniu nowej biblioteki ponowna modyfikacja! Pozostaje używanie biblioteki pod inna nazwą lub lepiej umieścić ja w projekcie.

Niestety, Arduino jest niedopracowane a twórcy nie dali możliwości aby "podpinać" się pod lub zastępować funkcje "systemowe". Zobacz kod, który generuje CubeMX na SMT32. prawie (niestety nie wszędzie) możesz wpiąć się swoja funkcją przed i po wykonaniu funkcji "systemowej" z czego nagminnie korzystam a jak chcesz, to możesz swoją funkcją zastąpić funkcje "systemową".

Link do komentarza
Share on other sites

1 godzinę temu, Elvis napisał:

Czyli volatile nic w kodzie nie zmienia.

To zalerzy od kodu. Zmienna volatile nie jest optymalizowana (jak w Bascom), zawsze (między innymi SFR, np GPIO) jest zapisywana/odczytywana gdy pojawia się w kodzie. W przypadku gdy nie ma atrybutu volatile zmienna może być przechowywana w rejestrze co pozwala na optymalizację kodu. Gdy np w przerwaniu zmieniana jest zmienna globalna (np licznik), nie zadeklarujemy zmiennej z atrybutem volatile kod

while( zmienna < xx ) {
 ... 
  }

na 90% zawiesi program, bo zmienna prawdopodobnie trafi do rejestru i w kolejnych obiegach porównanie będzie z rejestrem a nie ze zmienna a rejestr będzie zawierał wartość taką jak w chwili wejścia do pętli.

Dlaczego więc nie zadeklarować wszystkich zmiennych z atrybutem volatile? Bo z C zrobimy Bascom. Zmiennych nie da się zoptymalizować. Można spróbować zadeklarować  wszystkie zmienne z atrybutem volatile i bez. Po skompilowaniu bez volatile kod będzie krótszy i wykonuje się szybciej.

Problem volatile dotyczy także zewnętrznej przestrzeni XRAM w przypadku gdy są tam układy IO. W przypadku SFR kompilator "wie", żeby traktować ten obszar jak volatile ale w przypadku zewnętrznej przestrzeni trzeba zadbać o to samemu. Tu polecam próby lub zapoznanie się wynikami kompilacji, kod

for(uint8_t x=0; x<8; x++){
  XRAM = 1 << x;
  delay( 1000 );
}

nie zadział zgodnie z oczekiwaniem i nie będzie "wędrującej" jedynki na porcie wyjściowym (zakładam, że adres XRAM to port wyjściowy, np zatrzask 74xx574), prawdopodobnie znajdzie się tam wartość 0x01 (może 0x08) - zainteresowani sprawdzą.

 

5625

1 godzinę temu, Elvis napisał:

przywracającym ich obsługę mamy 4 instrukcje odczytu z pamięci lds. Przerwania są więc wyłączone na jakieś 5 cykli?

LDS wykonuje się w dwa cykle. CLI, IN, OUT w jeden. Przerwania są wiec zawieszona na 9 cykli czyli dokładnie 5625ns dla zegara 16MHz. Dlaczego 9 a nie 8? Dlatego, ze po SEI, musi wykonać się co najmniej 1 rozkaz zanim obsłużone zostanie przerwanie.

 

1 godzinę temu, Elvis napisał:

I stąd moje kolejne pytanie - jaki wpływ ma mieć wyłączenie przerwań na tak krótki czas?

W 1-Wire overdire dla slave jest zabójcze. Przy obsłudze overdrive nie można blokować przerwań. Jak więc odczytać np millis? Zrobić dwa odczyty, jak są takie same ok, jak złe powtórzyć.

Link do komentarza
Share on other sites

Zarejestruj się lub zaloguj, aby ukryć tę reklamę.
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.

jlcpcb.jpg

jlcpcb.jpg

Produkcja i montaż PCB - wybierz sprawdzone PCBWay!
   • Darmowe płytki dla studentów i projektów non-profit
   • Tylko 5$ za 10 prototypów PCB w 24 godziny
   • Usługa projektowania PCB na zlecenie
   • Montaż PCB od 30$ + bezpłatna dostawa i szablony
   • Darmowe narzędzie do podglądu plików Gerber
Zobacz również » Film z fabryki PCBWay

Nie wnikając w kwestie światopoglądowe proponuję sprawdzić samemu jak będzie lepiej:

typedef struct mystruct{
  unsigned long lv1;
  unsigned long lv2;
  unsigned long lv3;
  unsigned long lv4;
  unsigned char cv1;
}MStruct;

// zmienne globalne

static volatile MStruct ms;
static char var1;

MStruct 
foo1(unsigned char x){
  
  /* 50 odwołań do składników struktury ms
  	 20 odwołań do zmiennej var1
  */
  
  return ms;
}

MStruct 
foo2(unsigned char x){
  MStruct ms_l = ms; char var1_l = var1;
  
  /* 50 odwołań do składników struktury ms_l
  	 20 odwołań do zmiennej var1_l
  */
  ms = ms_l; var1 = var1_l
    
  return ms_l;
}
  

Obie funkcje robią dokładnie to samo, jednak zużycie pamięci w drugim przypadku jest wyraźnie mniejsze zakładając stosowanie poziomu optymalizacji s.

Nie twierdzę, że arduino jest "złe bo tak" ale jeśli można poprawić jakość własnego oprogramowania to chyba warto to robić szczególnie kiedy nie wymaga to jak widać wielkiego wysiłku a jedynie chęci zrozumienia niektórych przypadków.

Głupio mi bo sam sprowokowałem kolejny temat do przepychanek ale może coś pozytywnego z niego wyniknie.

Link do komentarza
Share on other sites

@atMegaTona wydaje mi się , ze nie wiesz do czego służy volatile. Przeczytaj post wyżej. Napisałem jakie są konsekwencje użycia volatile i dlaczego oraz kiedy trzeba użyć tego atrybutu a kiedy nie. W przypadku millis czytasz 4 bajty a AVR jest 8-bit więc robi to na 4 razy. Przypuśćmy, że licznik ms ma wartość 0x2FFFFFFF. Czytasz pierwszy kolejno od najmłodszego 0xFF, 0xFF, 0xFF licznik w przerwaniu zmienia wartość na 0x30000000, czytasz ostatni bajt 0x30. Jaki masz rezultat?  0x30FFFFFF. Jaki powinien być? 0x2FFFFFFF. Widzisz różnicę i problem? Gdy zawiesisz przerwania, do czego najlepiej użyć ATOMIC_BLOK, o którym już pisałem (jak postu nie ma to pisać do admina),  to odczytasz 0x2FFFFFFF o po odczycie, przerwanie zmieni stan na na 0x30000000 i wszystko "git". Jak widzisz, niezablokowanie przerwań powoduje problem w przypadku np porównań. Czekasz na wartość np 0x30F00000, a już przy 0x2FFFFFFF wynik porównania będzie pozytywny.

Aktualizacja:

Problem jest mniejszy gdy używasz wirtualnych timerów (z czego nagminnie korzystam, bo bywa, że mam ich 30 a tyle timerów to chyba żaden uC nie ma, o zaletach owych timerów już pisałem) gdy odliczasz w dół a porównujesz z wartością 0. Gdy licznik zawiera 0x0100 odczytasz młodszy 0x00, licznik zmieni wartość na 0x00FF, odczytasz starszy 0x00 więc masz 0x0000 zamiast 0x00FF. Gdy odmierzasz długie czasy rzędu kilkunastu sekund to różnica 256ms najczęściej nie jest istotna, gdy mierzysz do 255ms problemu nie ma bo używasz licznika 1 bajtowego, najgorzej, gdy mierzysz krótkie czasy, do kilku sekund (choćby timeout na konwersję temperatury dla DS18B20), wtedy te 256ms może mieć duże znaczenie. Co gorsza, najczęściej, błąd będzie występował bardzo rzadko (tak samo jak i w przypadku millis bez blokowania przerwań) i może być bardzo trudny do znalezienia.

Z wirtualnymi timerami liczącymi w dół masz ten luksus, że tam gdzie trzeba użyjesz ATOMIC_BLOK a tam gdzie nie jest to wymagane nie.

Pisałem o tym, jak odczytywać np millis bez zawieszania przerwań. Nie trzeba czytać dwa razy wszystkich 4 bajtów i porównywać wszystkich 4. Starsze pokolenie zna układ  6821 czy 6526 i podobne (np używany w Amidze ulepszony 6526). Timery nie miały rejestru tymczasowego (przeciętny użytkownik arduino nie ma o tym pojęcia) jak w AVR, gdzie czytając/zapisując daną jest ona zapamiętywana w zatrzasku i nie grozi jego zmiana w czasie zapisu/odczytu. W przypadku 6821 i podobnych producent zalecał odczytać młodszy bajt, starszy, sprawdzić czy młodszy nie uległ zmianę jest tak operację powtórzyć. W przypadku AVR można postąpić tak samo, odczytać 4 bajty, ponownie odczytać najmłodszy i porównać. Kod będzie krótszy i wykona się szybciej.

Link do komentarza
Share on other sites

Słowo wyjaśnienia dlaczego wspomniałem o funkcji millis w ogóle. Otóż pisałem prosty program generujący pwm na wielu pinach i zaczęła mnie bardzo irytować konieczność częstego sprawdzania czy zadany czas już upłynął więc wpadłem na pomysł napisania sobie tablicy delayów i sprawdzania wszystkich na raz w osobnej funkcji wywoływanej na początku pętli. Okazało się jednak, że z powodu długości zmiennych UL i wyłączania przez funkcję millis przerwań mimo, że na krótko moje pwm'y po pewnym czasie się rozjeżdżają przez co najadłem się też wstydu bo w pośpiechu nawet nie przeszło mi przez myśl, że na dłuższą metę będzie coś nie tak jak powinno a to z resztą już nie pierwszy raz kiedy arduino wykręca mi tego typu numer. Nie mogąc zrezygnować z innych przerwań ponieważ były krytyczne przepisałem na nowo cały mechanizm generowania interwałów czasowych i zamiast UL zastosowałem 16 bitową zmienną dla porównania, rejestry sprzętowe do przechowywania aktualnego czasu i tablicę delay'ów dla każdego przypadku mierzenia czasu a fakt upłynięcia zadanego czasu dla jakiegoś delaya sygnalizowałem osobnymi lokalnymi flagami co poskutkowało tym, że nie tylko pwm'y przestały się rozjeżdżać (nawet to policzyłem w rezultacie co do taktu) ale i rozmiar kodu wynikowego znacznie się zmniejszył ale ile czasu na to zmarnowałem to swoją drogą dlatego padło takie stwierdzenie że millis jest złe w innym wątku od którego wziął swój tytuł ten temat.

Link do komentarza
Share on other sites

Ciekawa sprawa z tymi PWM-ami, może dałoby się krótki programik przykładowy napisać, żeby pokazać kiedy to występuje? 

Natomiast co do ostatniego zdania, które dopisałem we wpisie o volatile to chodziło mi o ten konkretny przypadek, przecież napisałem wcześniej że volatile wyłącza optymalizację - co oczywiście może mieć wpływ na kod wynikowy. Zostawmy może to czepianie się słówek - w sumie to ciekawa dyskusja, a wpływ millis() na PWM bardzo interesujący. Tylko proponuję wyłączyć niepotrzebne napinanie się i kłótnie - może wtedy zamiast marnować czas i siły nauczymy się od siebie czegoś interesującego.

Link do komentarza
Share on other sites

37 minut temu, atMegaTona napisał:

moje pwm'y po pewnym czasie się rozjeżdżają

Pewnie programowe? Jak tak użyj sprzętowych i problem zniknie albo czytaj licznik millis w sposób jaki przedstawiłem wcześniej, bez wyłączania przerwań.

Aktualizacja:

Swoją "szosą" musiałeś ostro katować kod tym millis.

Link do komentarza
Share on other sites

1 godzinę temu, RFM napisał:

@atMegaTona wydaje mi się , ze nie wiesz do czego służy volatile.

Kiedy pisałem poprzedni post Twojego jeszcze nie było. Wiem do czego volatile służy i wiem też, że lepiej jest sobie taką zmienną przepisać do lokalnej i wykonywać działania na lokalnej a na koniec zaktualizować volatile jeśli trzeba, tak jak w tym przykładzie na szybko napisanym ale w tej kwestii się chyba zgadzamy.

unsigned long millis()
{
	unsigned long m;
	uint8_t oldSREG = SREG;

	// disable interrupts while we read timer0_millis or we might get an
	// inconsistent value (e.g. in the middle of a write to timer0_millis)
	cli();
	m = timer0_millis;
	SREG = oldSREG;

	return m;
}

unsigned long micros() {
	unsigned long m;
	uint8_t oldSREG = SREG, t;
	
	cli();
	m = timer0_overflow_count;
#if defined(TCNT0)
	t = TCNT0;
#elif defined(TCNT0L)
	t = TCNT0L;
#else
	#error TIMER 0 not defined
#endif

#ifdef TIFR0
	if ((TIFR0 & _BV(TOV0)) && (t < 255))
		m++;
#else
	if ((TIFR & _BV(TOV0)) && (t < 255))
		m++;
#endif

	SREG = oldSREG;
	
	return ((m << 8) + t) * (64 / clockCyclesPerMicrosecond());
}

void delay(unsigned long ms)
{
	uint32_t start = micros();

	while (ms > 0) {
		yield();
		while ( ms > 0 && (micros() - start) >= 1000) {
			ms--;
			start += 1000;
		}
	}
}

To są te funkcje z arduino i jak widać millis wyłącza przerwania (i całkiem słusznie z resztą) ale niekiedy nie jest to pożądane i może prowadzić do jakichś dziwnych przypadków a na początku zabawy z arduino mało kto w ogóle zagląda do tych źródeł więc może nie wiedzieć. W funkcji micros jak widać jest zrobione dokładnie tak jak o tym pisałem. Funkcja delay z kolei to wg. mnie jakieś zupełne nieporozumienie. Dla samej przyzwoitości powinna być napisana w asm, z resztą te dwie poprzednie również.

W moim przypadku miała miejsce taka sytuacja, że w pętli głównej nie było już nic do zrobienie więc przez większość czasu był sprawdzany upływ czasu funkcją millis na każdym przypadku i to był główny powód rozjeżdżania pwm'ów. Nie wiedząc czy nie pamiętając jak to działa w szczegółach raczej trudno się jest domyślić co nie gra.

Link do komentarza
Share on other sites

34 minuty temu, atMegaTona napisał:

w pętli głównej nie było już nic do zrobienie więc przez większość czasu był sprawdzany upływ czasu funkcją millis

W takim przypadku usypia się CPU do czasu wystąpienia przerwania. Mniejsze zużycie energii, mniejsze zaburzenia EMC.

34 minuty temu, atMegaTona napisał:

Dla samej przyzwoitości powinna być napisana w asm, z resztą te dwie poprzednie również.

A co to by dało?

34 minuty temu, atMegaTona napisał:

Funkcja delay z kolei to wg. mnie jakieś zupełne nieporozumienie.

Fakt, ze nie powinno jej być. W bibliotekach SPL dla STM32 takiej funkcji (jak pamiętam) nie było ale w HAl już jest. Czasem, przy testach, pierwszym uruchomieniu, przydaje się ale w finalnym kodzie, nie powinno jej być. Można ją dopuścić w pewnych szczególnych przypadkach podczas startu programu, przed pętlą główną.

Link do komentarza
Share on other sites

Niestety - delay jest w standardzie wiringa i musi zostać; co nie znaczy że trzeba to wpychać w każde miejsce w programie i burczeć że źle działa.

To jest jak z goto - jeśli umiesz używać to czasem jest to najlepsze rozwiązanie. Problem jest tylko w "trzeba umieć"...

 

Link do komentarza
Share on other sites

Jakoś nie zauważyłem. Rozwiązanie problemu powinno zacząć się od sprawdzenia, czy takowy w ogóle istnieje. Na razie (oprócz sytuacji, które ewidentnie wykraczają poza możliwości wiringa) takich nie stwierdziłem.

No, ale pewnie wielu rzeczy nie wiem 😞

Link do komentarza
Share on other sites

Dołącz do dyskusji, napisz odpowiedź!

Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!

Anonim
Dołącz do dyskusji! Kliknij i zacznij pisać...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.

×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.