AVR ATmega, pomiar częstotliwości przebiegu,

W dniu 10.02.2011 16:16, Zbych pisze:

Chyba nie - definicja wyglądają tak: # define sei() __asm__ __volatile__ ("sei" ::) # define cli() __asm__ __volatile__ ("cli" ::)

Co zabrania kompilatorowi zamiany kolejności ewaluacji wyrażeń.

Dziwne swoją drogą, że nie jest to zapisane na wszelki wypadek jako # define sei() __asm__ __volatile__ ("sei" :::"memory") ale może to wynika z tego, że ATOMIC_BLOCK robi barierę.

Zwłaszcza, że przy ATOMIC_BLOCK trudniej o pomyłkę i od razu widać gdzie jest synchronizacja.

Reply to
Michoo
Loading thread data ...

Dziękuję za odpowiedź.

Racja.

Tego trochę nie rozumiem. Czy nie masz może kodu, który realizuje taki 24-, 32-bitowy licznik? Niestety, ale nie mogę nic znaleźć, a szukam już kilka godzin :(

R.

Reply to
Robbo

W dniu 2011-02-10 17:00, Michoo pisze:

volatile zabrania usunięcia, optymalizacji, ale nie zabroni przesunięcia czegoś co jest pomiędzy sei i cli, czyli z kodu cli();[coś];sei(); może wyjść: cli();sei(); [coś];

Dodatkowo taka konstrukcja nie zmusza kompilatora do zapisania wartości tymczasowych trzymanych w rejestrach do pamięci, więc może się okazać, że zapis wielobajtowej zmiennej nastąpi już przy włączonych przerwaniach.

Przykłady można znaleźć na liście dyskusyjnej avr-gcc.

No właśnie o tę barierę na pamięci chodzi.

Reply to
Zbych

W dniu 2011-02-10 19:15, Robbo pisze:

Mniej więcej (raczej mniej niż więcej) widziałbym to tak:

uint32_t period; uint8_t overflow;

SIGNAL (SIG_INPUT_CAPTURE3) { static uint32_t prev = 0; union{ struct{ uint16_t byte01; uint8_t byte2; uint8_t byte3 }s; uint32_t dword; }now;

uint32_t now.s.byte01 = ICR3;

if (przepełnienie){ overflow++; skasuj_przepełnienie; } now.s.byte2 = overflow; now.s.byte3 = 0; period = now.dword - prev; prev = now.dword; }

SIGNAL (SIG_INPUT_OVERFLOW3) { overflow++; }

Nie chce mi się teraz szukać w dokumentacji jak się nazywają rejestry z flagami od przepełnienia, więc musisz fragment ze sprawdzaniem przepełnienia odpowiednio uzupełnić.

Reply to
Zbych

Dziękuję bardzo za ten kod. Przykład z wykorzystaniem union oraz struct również bardzo cenny!

Twój kod, tak na szybko, bez union i struct (chodzi o samą ideę):

SIGNAL (SIG_INPUT_CAPTURE3) { icr = ICR3;

if (ETIFR & _BV(TOV3)) { overflow++; ETIFR |= _BV(TOV3); ff++; // licznik zdarzeń tego typu }

actualDuration = ((unsigned long)icr + overflow * 65536UL) - pp; pp = (unsigned long)icr + overflow * 65536UL; }

SIGNAL (SIG_OVERFLOW3) { overflow++; }

Wyświetlam sobie na LCD wartość ff. Na ogół ma wartość "1" i tak już zostaje. Wynika z tego, że bardzo rzadko mam sytuację z jednoczesnymi przerwaniami.

Wydaje mi się, że możliwe jest jeszcze pewne uproszczenie -- w praktyce mi ono zdaje egzamin (a przynajmniej tak mi się wydaje na podstawie obserwacji). Chodzi o to, że u Ciebie "overflow" inkrementowany jest nieustannie. To nie jest błąd -- wszystko działa jak należy. Niemniej ja chciałem mieć "overflow" zerowany, gdyż zależnie od jego wartości chciałem zrobić odcinanie niskich częstotliwości mierzonego sygnału. Oto przeróbka:

*** KROK 1

SIGNAL (SIG_INPUT_CAPTURE3) { icr = ICR3;

if (ETIFR & _BV(TOV3)) { overflow++; ETIFR |= _BV(TOV3); }

actualDuration = ((unsigned long)icr + overflow * 65536UL) - pp; pp = (unsigned long)icr + overflow * 65536UL;

pp -= overflow * 65536UL; overflow = 0; }

powyższe można uprościć do takiej postaci:

*** KROK 2

SIGNAL (SIG_INPUT_CAPTURE3) { icr = ICR3;

if (ETIFR & _BV(TOV3)) { overflow++; ETIFR |= _BV(TOV3); }

actualDuration = ((unsigned long)icr + overflow * 65536UL) - pp; pp = (unsigned long)icr;

overflow = 0; }

Zatem wydaje mi się (może się mylę, ale w praktyce działa i wygląda OK), że w "pp" nie musimy trzymać wartości licznika software'owego, a wystarczy trzymać ICR. Co o tym myślisz?

A oto kompletny kod -- odcinamy poniżej ok. 2Hz (w szczególności, gdy sygnał w ogóle jest zerowy).

SIGNAL (SIG_INPUT_CAPTURE3) { icr = ICR3;

if (ETIFR & _BV(TOV3)) { overflow++; ETIFR |= _BV(TOV3); }

actualDuration = ((unsigned long)icr + overflow * 65536UL) - pp; pp = (unsigned long)icr;

overflow = 0; }

SIGNAL (SIG_OVERFLOW3) { overflow++; }

int main(void) { char buf[16]; unsigned long actualDurationLatch;

TCCR3A = 0; TCCR3B |= _BV(ICES3) | _BV(ICNC3) | _BV(CS32) | _BV(30); // prescaler 64 ETIMSK |= _BV(TICIE3) | _BV(TOIE3);

sei();

while (1) { cli(); actualDurationLatch = actualDuration; sei();

if ((overflow >= 5) || (actualDurationLatch > 150000)) { LCDwriteString("-------------"); } else { sprintf(s, "%ld ", actualDurationLatch); LCDwriteString(s); } } }

W moim pierwotnym liście podałem kod z wykorzystaniem SIG_OUTPUT_COMPARE2 oraz SIG_INTERRUPT7. Kod tamten miał (w porównaniu do tego z wykorzystaniem input capture) tę wadę, że zwiększanie zmiennej "timer" 100000 razy na sekundę obciążąło procesor. Niemniej miał on też pewną zaletę -- chodzi o to, że dla niskich częstotliwości "odcinanie" miałem zawsze w określonym czasie (np. gdy timer osiągnął wartość 100000; timer był resetowany po każdym zboczu narastającym -- zatem wartość 100000 mówiła, że minęło 100000 ticków zegara od ostatniego zbocza narastającego sygnału mierzonego). Tu (chodzi o kod z input capture) niestety jest problem. Gdy sygnał zaniknie nagle, to "overflow" musi doliczyć do 5 (taką wartość sobie przyjąłem), ale przecież ICR może mieć różną wartość i czas, w którym "overflow" doliczy do

5 może być różny (gdy ICR było bliskie 0, to będzie to czas prawie pięciu przepełnień ICR; gdy ICR było prawie 65000, to będzie to czas nieco ponad czterech przepełnień ICR). Aby mieć dokładne odcinanie (po upływie dokładnie określonego czasu), musiałbym zrobić dodatkowy timer (np. dający impuls co 1 sekundę), resetowany w SIG_INPUT_CAPTURE3. Jeśli timer da impuls (czyli nie został zresetowany w ciągu ostatniej sekundy), to znaczy, że sygnał jest poniżej 1Hz i robimy odcinanie.

R.

Reply to
Robbo

W dniu 10.02.2011 19:35, Zbych pisze:

Tylko jeżeli [coś] jest kiepskim kodem ;) - Używanie w sekcji krytycznej czegoś co nie jest volatile i nie zawiera bariery na przesyłanej zmiennej jest sprzeczne zarówno ze standardem jak i ze zdrowym rozsądkiem. Gdy [coś] operuje na zmiennych volatile wszystko musi działać a jednocześnie nie ma konieczności synchronizacji wszystkich rejestrów jak przy klasycznej barierze.

Masz może linkę? Bo na szybko nie mogłem znaleźć.

Tylko bariera jest wolna i nieoptymalna. W sumie jak się głębiej zastanowić to to jest sensowne: cli();[operacje na volatile]sei();

- działa jak należy cli();[dowolne operacje i synchronizacja zmiennej współdzielonej]sei();

- też działa cli();[operacje bez synchronizacji]sei();

- robi to co programista napisał a nie to co chciał, ale C jest znane z tego że pozwala się postrzelić w stopę

Reply to
Michoo

W dniu 2011-02-10 22:31, Michoo pisze:

Pokaż mi gdzie jest to napisane w standardzie. Ja wtykanie volatile wszędzie gdzie się da uważam co najmniej za lenistwo. Jak już muszę użyć to często robię unię zmiennej volatile i bez volatile. Dzięki czemu nie mam nadmiarowego kodu np. w przerwaniach.

Nie chce mi się szukać. Musisz przejrzeć daty w okolicach dodania makr ATOMIC do biblioteki. Te makra powstały właśnie po tym jak ludzie zaczęli zgłaszać błędy związane przenoszeniem operacji poza blok cli()-sei().

Ta bariera nie jest wolniejsza niż używanie volatile. Wymusza tylko zrzut i pobranie zmiennych.

Tia, temat na kolejne bicie piany :-)

Reply to
Zbych

Gdybyś mógł wyjaśnić, co daje taka konstrukcja...?

R.

Reply to
Robbo

W dniu 11.02.2011 08:36, Zbych pisze:

C++: When the processing of the abstract machine is interrupted by receipt of a signal, the values of objects with type other than volatile sig_atomic_t are unspecified, and the value of any object not of volatile sig_atomic_t that is modified by the handler becomes undefined.

Czyli przerzucanie program<->przerwanie czegoś innego niż "volatile sig_atomic_t" w myśl standardu nie jest zdefiniowane. Ale jednocześnie:

Accessing an object designated by a volatile lvalue (3.10), modifying an object, calling a library I/O function, or calling a function that does any of those operations are all side effects, which are changes in the state of the execution environment. Evaluation of an expression might produce side effects. At certain specified points in the execution sequence called sequence points, all side effects of previous evaluations shall be complete and no side effects of subsequent evaluations shall have taken place.)

W związku z tym gcc/g++ daje gwarancję na prawidłowe przekazanie obiektów volatile.

formatting link

Ja za błąd w sztuce - nie dość, że optymalizacje psuje to jeszcze wprowadza bardzo trudne do wykrycia błędy (przesyłanie wielobajtowych obiektów volatile bez synchronizacji a jedynie licząc, że samo volatile wystarczy) Niemniej *nie oznaczenie* obiektu przekazywanego jako volatile to już lampka ostrzegawcza, bo trzeba dodatkowo pamiętać o barierach.

Ja zazwyczaj używam volatile do samego przekazania i osobne zmienne "po obu stronach" albo nie daję volatile i pamiętam o synchronizacji ;)

Ok, będę miał chwilę to poszukam.

Wszystkich zmiennych. Używanie volatile jest wolne tylko w odniesieniu do tego jednego obiektu.

Napisałem kiedyś makro, które robiło barierę na podanej zmiennej - dziwię się, że nie ma czegoś takiego w bibliotece: #define SYNC_VARIABLE(var) __asm__ __volatile__ ("" :"=m"(var):"m"(var))

Po coś te grupy dyskusyjne powstały ;)

Reply to
Michoo

W dniu 2011-02-11 12:43, Michoo pisze:

Co prawda c++, a nie c, ale dzięki za podesłanie tego. Miło wiedzieć, że jest to w ogóle ustandaryzowane.

Fajne :-)

Reply to
Zbych

Zrobiłem tak:

while (1) { ATOMIC_BLOCK(ATOMIC_FORCEON) { actualDurationLatch = actualDuration; }

sprintf(s, "%d %ld ", i++, actualDurationLatch); LCDwriteString(s); }

W Makefile dałem -std=gnu99.

Efekt jest taki, że na wyświetlaczu widzę cały czas zwiększającą się wartość zmiennej "i". Natomiast actualDurationLatch przyjmuje jakąś wartość i tak już trwa -- nie reaguje na zmianę częstotliwości. Gdy wyrzucę ATOMIC_BLOCK, to actualDurationLatch ożywa.

R.

Reply to
Robbo

W dniu 2011-02-11 12:05, Robbo pisze:

W ogólności krótszy/szybszy kod. Użycie volatile to zakaz optymalizacji dla kompilatora. Poniżej masz prosty przykład z odmierzaniem opóźnienia przez przerwanie timera. Gdyby zmienna była volatile, to w przerwaniu musiałbyś ją pobrać dwa razy.

union{ volatile uint8_t V; uint8_t NV; }aqq;

ISR(od_jakiegos_timera){ if (aqq.NV != 0) aqq.NV--; }

main(){

aqq.V = 100; while(aqq.V != 0); }

Reply to
Zbych

W dniu 2011-02-11 13:22, Robbo pisze:

Dopisz do swojego programu dwie linie:

while (1) { asm volatile(";ala ma kota - start"); //<---------------- ATOMIC_BLOCK(ATOMIC_FORCEON) { actualDurationLatch = actualDuration; } asm volatile(";ala ma kota - stop"); //<----------------

sprintf(s, "%d %ld ", i++, actualDurationLatch); LCDwriteString(s); }

Skompiluj, otwórz plik *.lss, który zawiera program po deasemblacji, poszukaj ciągu "ala ma kota" i porównaj czym wersja z ATOMIC różni się od wersji bez ATOMIC.

Reply to
Zbych

Dzięki za wyjaśnienie. Sprawdziłem...

union{ volatile uint8_t V; uint8_t NV; }aqq;

ISR(od_jakiegos_timera){ if (aqq.NV != 0) aqq.NV--; }

main(){

aqq.V = 100; while(aqq.V != 0); }

Daje kod dla ISR(): push r1 push r0 in r0, 0x3f push r0 eor r1, r1 push r24

lds r24,0x0100 and r24, r24 breq .+6 subi r24, 0x01 sts 0x0100, r24

pop r24 pop r0 out 0x3f, r0 pop r0 pop r1 reti

----------------

Natomiast program:

volatile uint8_t V;

ISR(od_jakiegos_timera){ if (V != 0) V--; }

main(){

V = 100; while(V != 0); }

Daje kod dla ISR(): push r1 push r0 in r0, 0x3f push r0 eor r1, r1 push r24

lds r24,0x0100 and r24, r24 breq .+10 lds r24, 0x0100 subi r24, 0x01 sts 0x0100, r24

pop r24 pop r0 out 0x3f, r0 pop r0 pop r1 reti

Reply to
Robbo

Z ATOMIC i bez ATOMIC wygląda tak samo:

lds r24, 0x052f lds r25, 0x0530 lds r26, 0x0531 lds r27, 0x0532 sts 0x029e, r24 sts 0x029f, r25 sts 0x02a0, r26 sts 0x02a1, r27

Ale zauważyłem (patrząc w plik main.lst), że użycie ATOMIC spowodowało, iż przed pętlą while (1) znalazło się iCliRetVal:

static __inline__ uint8_t __iCliRetVal(void) { cli();

while (1) {

asm volatile(";ala ma kota - start"); //<-------- ATOMIC_BLOCK(ATOMIC_FORCEON) { actualDurationLatch = actualDuration; ...

R.

Reply to
Robbo

ElectronDepot website is not affiliated with any of the manufacturers or service providers discussed here. All logos and trade names are the property of their respective owners.