AVR ATmega, pomiar częstotliwości przebiegu,

Witam,

Jakiś czas temu pytałem o sprawy związane z pomiarem częstotliwości sinusoidy o częstotliwości od 2 do 150Hz oraz zmiennej amplitudzie (od kilku V do 400V). Mam już układ elektroniczny przekształcający sinusoidę na prostokąt (brana jest pod uwagę tylko dodatnia połówka, a ujemna jest usuwana). Jest tu kilka rezystorów, mostek, tranzystor, dioda oraz transformatorek. (Może działanie tego układu da się jeszcze ulepszyć, ale to w sumie nie jest teraz tematem). Uzyskany z dodatniej połówki sinusoidy prostokąt jest podawany na nóżkę PE7(INT7) mikrokontrolera ATmega128 (kwarc

16MHz). Dokładność pomiaru, którą chciałbym osiągnąć to 0,2Hz przy częstotliwości 50Hz. Chciałem Was prosić o sprawdzenie mojego kodu, którego zadaniem jest nieustanne mierzenie czasu trwania dodatniej połówki, a następnie wypisywanie tej wartości na wyświetlacz. Kod, który napisałem wygląda na działający, ale może ma jakiś ukryty błąd.

volatile unsigned long int timer = 100000; volatile short actualTime;

./* procedura wywoływana 100000 razy na sekundę */ SIGNAL (SIG_OUTPUT_COMPARE2) { cli();

if (timer < 100000) timer++;

sei(); }

/* wyzwalane zboczem narastającym i opadającym */ SIGNAL (SIG_INTERRUPT7) { cli();

if (PINE & PINE7) { timer = 0; TCCR2 = 0; // stop timera TCNT2 = 0; // zerowanie licznika TCCR2 = _BV(WGM21) | // start timera _BV(C21); // (prescaler 8) } else { actualTime = timer; }

sei(); }

int main(void) { char s[16];

initLCD();

cli();

EICRB |= _BV(ISC70); EIMSK |= _BV(INT7);

TCCR2 = 0; OCR2 = 19; TIFR |= _BV(OCF2); TIMSK |= _BV(OCIE2); while (ASSR & _BV(OCR2UB)) ;

sei();

while (1) {

if (timer == 100000) LCDshowString("---"); else { sprintf(s, "%d ", actualTime); LCDshowString(s); } }

return 0; }

*** Jak to się sprawuje: Jako źródło sygnału podłączyłem po prostu zasilanie sieciowe 230V/50Hz. Wczoraj wieczorem przed godziną 22 liczba na wyświetlaczu wynosiła 996 (ostatnia cyfra niekiedy migała na "5"). Potem zaczęło to rosnąć... 997/8. O godzinie 22 było jakieś 999. Potem zaczęło spadać do 996. Teraz (godzina 13:40) mam 996/7. ***

W szczególności chciałem zapytać o to, czy poprawnie zastosowałem cli() i sei() w procedurach obsługi przerwań. Ponadto chciałem zapytać, czy kod wewnątrz while(1) jest poprawny i czy nie lepiej byłoby zapisać to tak:

while (1) { unsigned long int timerLatch; short actualTimeLatch;

cli(); timerLatch = timer; actualTimeLatch = actualTime; sei();

if (timerLatch == 100000) LCDshowString("---"); else { sprintf(s, "%d ", actualTimeLatch); LCDshowString(s); } }

Chodzi o sytuację, w której w pętli głównej jest przetwarzana wartość zmiennej timer albo actualTime (np. został odczytany pierwszy bajt) i w trakcie nastąpi przerwanie, zmieni wartość zmiennej, obsługa przerwania się skończy, zostanie dokończone przetwarzanie zmiennej timer albo actualTime -- wartość chyba będzie błędna (jeden bajt z poprzedniej wartości, reszta bajtów po aktualizacji w przerwaniu).

Z góry dziękuję za rady.

Robbo

Reply to
Robbo
Loading thread data ...

Ja bym liczył czas pomiedzy dwoma narastającymi zboczami. Ale ja herbatę cukrem...

[...]
Reply to
RoMan Mandziejewicz

W dniu 09.02.2011 13:54, Robbo pisze:

Nie.

Jeżeli nie deklarujesz przerwań explicite jako nieblokujących to one dbają o wyłączność za Ciebie. Pisząc sei na końcu przerwania umożliwiasz odpalenie przerwania po 2 cyklach a prawdopodobnie wyjście z przerwania trwa chwilę dłużej - przy dużym nagromadzeniu przerwań do obsługi skończy się to przepełnieniem stosu.

Reply to
Michoo

Użytkownik "Robbo" snipped-for-privacy@mmm.com napisał w wiadomości news:4d528e6b$0$2436$ snipped-for-privacy@news.neostrada.pl...

Widzisz, tak to jest jak się nigdy żadnego kodu w assemblerze na AVR-a nie napisało.

Otóż procesor AVR wchodząc w obsługę przerwania sam wyłącza przerwania. Więc stan jest taki, że do czasu zakończenia obsługi aktualnego przerwania, obsługa kolejnych przerwań jest wyłączona.

Więc cli() po wejściu w obsługę przerwania nie ma sensu (bo przerwania już są wyłączone), natomiast sei() dajesz wtedy jak chcesz włączyć ręcznie obsługę przerwań, co oczywiście jest możliwe, tylko na stos trzeba uważać (jeśli tych przerwań jest dużo w jednostce czasu).

sei() na końcu też nie ma sensu, ponieważ procedury obsługi przerwania nie opuszcza się poprzez ret, tylko poprzez reti, będące niczym innym jak połączeniem: ret + sei.

Reply to
Marcin Wasilewski

truskawki :P

... no chyba ze znamy inne dowcipy

c.

Reply to
Cezar
[...]

Truskawki to ja ze śmietaną, najlepiej bitą.

To nie dowcip.

Reply to
RoMan Mandziejewicz

Nie bawiłem się jeszcze przerwaniami w atmedze i muszę zaraz sprawdzić w nocie jak jest ale zapytam wybiegiem a czy aby nie ma przypadkiem zatrzasku przerwań przychodzących podczas obsługi przerwań i priorytetów przerwań?

Marek

Reply to
4CX250

Użytkownik "Robbo" snipped-for-privacy@mmm.com napisał w wiadomości news:4d528e6b$0$2436$ snipped-for-privacy@news.neostrada.pl...

A może wykorzystac dwa liczniki. Jeden zliczający uformowane impulsy a drugi z autoresetem wyzwalający przerwanie co określony czas. W przerwaniu sprawdzałbym stan licznika zliczającego, następnie kasował go i opuszczał przerwanie.

Marek

Reply to
4CX250

Użytkownik "RoMan Mandziejewicz" snipped-for-privacy@pik-net.pl napisał w wiadomości news: snipped-for-privacy@pik-net.pl...

Pan policjant widzę chyba po służbie bo też pozwala sobie na NTG. Wiem wiem. Jedzie pan na sygnale jako pojazd uprzywilejowany.

Marek

Reply to
4CX250

W dniu 09.02.2011 13:54, Robbo pisze:

Witam Na wstępie to się zapytam, na czym Koledze tak naprawdę zależy ? Na pomiarze czasu (okresu) czy częstotliwości ? Bo pisze Kolega o "dokładności 0,2Hz" i o "wyświetlaniu czasu trwania dodatniej połówki". Ponieważ zakres częstotliwości jest stosunkowo niewielki, można to zrobić na dwa sposoby i kilka wariacji ;-).

  1. Pomiar ilości impulsów w stałym przedziale czasu. Przy wymaganej rozdzielczości 0,2Hz pomiar musiałby trwać co najmniej 5 sekund. Metod realizacji jest wiele. Przy tak małej częstotliwości zbocze sygnału mierzonego można sobie wykrywać programowo w pętli (pod warunkiem, że w systemie nie istnieją przerwania, których obsługa zajmuje ponad 6ms). Jako bramkę czasu najprościej wykorzystać któryś z timerów. Bardziej elegancko liczyć zbocza w obsłudze zewnętrznego przerwania. Można zliczać impulsy sprzętowo za pomocą licznika 16-bitowego, a 8-bitowym realizować bramkę czasu. Można również całkowicie sprzętowo z wykorzystaniem rejestru ICR licznika 16-bitowego i licznika 8-bitowego generującego na wyjściu OCx impulsy o czasie bramkowania.
  2. Pomiar czasu między impulsami (okresu). Tu aż się prosi wykorzystać Input Capture Register.

Pozdrawiam Grzegorz

Reply to
Grzegorz Kurczyk

Dziękuję. A chciałem jeszcze prosić o odpowiedź na pytanie dotyczące użycia sei i cli w pętli głównej.

R.

Reply to
Robbo

Ja miodem...

Sprawdziłem. W tej chwili:

- mierzenie czasu między narastającym a opadającym zboczem dodatniej połówki daje czas 998/7.

- mierzenie czasu między narastającymi zboczami kolejnych dodatnich połówek daje czas 2000/1.

R
Reply to
Robbo

Za mocny smak...

Jak Ci się wydaje - który wynik jest bardziej zbliżony do rzeczywistości?

Reply to
RoMan Mandziejewicz

Wydaje mi się, że oczywiście ten drugi wynik :)

Ale, jeszcze trzeba będzie popracować nad elektroniką i może uda się wykrzesać coś więcej z metody nr 1 Może też ujemną połówkę uda się użyć i wtedy miałbym szybszy odczyt częstotliwości z niewielkim błędem.

R.

Reply to
Robbo

Owszem, nieprecyzyjnie napisałem. Mierzyć będę czas i na tej podstawie będzie działać układ. Użytkownikowi będę jednak prezentował częstotliwość wyznaczoną na podstawie zmierzonego czasu.

I to chyba będzie eleganckie rozwiązanie. Właśnie się tym zająłem i mam już efekty. Jutro pozwolę sobie wkleić kod do oceny.

R.

Reply to
Robbo

Użytkownik "Robbo" snipped-for-privacy@mmm.com napisał w wiadomości news:4d531cb8$0$2457$ snipped-for-privacy@news.neostrada.pl...

W wielkim skrócie - cli/sei używasz wtedy gdy dokonujesz zapisu/odczytu zmiennych, które: wykorzystujesz też w przerwaniach i cała operacja zajmuje więcej niż 1 operację procesora (zmienne dłuższe niż 1 bajt i operacje bitowe). No i oczywiście wtedy gdy z pewnych przyczyn (głównie czasowych) dana operacja nie powinna zostać przerwana obsługą przerwania.

Reply to
Marcin Wasilewski

Oto kod:

volatile unsigned short icr, prevIcr;

SIGNAL (SIG_INPUT_CAPTURE3) { icr = ICR3; acutalDuration = icr - prevIcr; prevIcr = icr; }

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

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

sei();

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

sprintf(s, "%u ", actualDurationLatch); LCDwriteString(s); } }

Na kwarcu 16MHz i prescalerze 64, ICR3 zwiększa się z częstotliwością

1/250000. Dla 50Hz na wyświetlaczu mam liczbę 5000. Mogę zejść do ok. 2Hz -- poniżej już się nie da, bo licznik 16-bitowy ICR3 się przepełnia. Jednak 2Hz jest dla mnie akceptowalnym wynikiem i nie potrzebuję już zwiększać prescalera, kosztem obniżenia dokładności pomiaru.

Chciałem zapytać

  1. Czy kod jest poprawny, a jeśli nie, to co poprawić?
  2. Jak poradzić sobie teraz z "odcinaniem" dla częstotliwości mniejszej niż
2Hz? Poniżej 2Hz licznik się przekręci i wskazany czas nie będzie poprawny; może być też sytuacja, gdy sygnał zaniknie (częstotliwość 0Hz). Muszę mieć o tym informację. W moim programie z timerem nie było problemu (pokazywały się "---" na wyświetlaczu dla zbyt niskich częstotliwości) -- tu nie wiem, jak to dobrze rozwiązać.

R.

Reply to
Robbo

W dniu 2011-02-10 16:09, Robbo pisze:

Nie używaj cli i sei do robienia sekcji atomowych, te makra nie są zabezpieczone przed optymalizacją i kompilator może zmienić kolejność instrukcji (choć oczywiście nie musi). Zamiast tego posłuż się ATOMIC_BLOCK

formatting link

Albo zmniejsz taktowanie timera, albo wykorzystaj przerwanie od przepełnienia do "dorobienia" trzeciego bajtu do licznika.

Reply to
Zbych

Czy chodzi o coś takiego (pisane z pamięci)?

volatile unsigned long icr, prevIcr; volatile char overflow;

SIGNAL (SIG_INPUT_CAPTURE3) { icr = ICR3; acutalDuration = icr - prevIcr + overflow * 0xffffUL; overflow = 0; prevIcr = icr; }

SIGNAL (SIG_INPUT_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();

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

Jeśli chodzi o coś takiego, to mi nie działa. "overflow" impulsuje czasem na wartość "1", bo ICR3 nieustannie się przepełnia. Czy ktoś może wskazać, jak poprawnie zastosować rozwiązanie z przepełnieniem? To mogłoby być dobre rozwiązanie "odcięcia" niskich częstotliwości, tylko nie wiem, jak poprawnie to zrealizować.

R.

Reply to
Robbo

W dniu 2011-02-10 16:27, Robbo pisze:

Po pierwsze jak obydwa przerwania (przechwytywanie i przepełnienie) zostaną zgłoszone jednocześnie, to będziesz miał nieprawidłowy wynik. Trzeba w przerwaniu od przechwytywania sprawdzać obecność nieobsłużonego przepełnienia. Zmienna prev powinno pamiętać nie tylko stan licznika sprzętowego, ale i software'owego. Zamiast mnożyć 65535 powinieneś mnożyć przez 65536, a zdecydowanie lepiej przesunąć liczbę o 16-bitów (albo jeszcze lepiej użyć unii liczby 32-bitowej z tablicą 4 bajtów, bo gcc ma słabą optymalizację przy obsłudze długich liczb na avr). Do tego trzeba pamiętać o ręcznym rzutowaniu na 32-bity przed przesunięciem, bo kompilator tego za ciebie nie zrobi (co najwyżej zrobi promocję do

16-bitów, bo tyle ma int).
Reply to
Zbych

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.