Pytanie:
Jak zachować dokładność millis () podczas korzystania z trybu ADC_sleep?
FarO
2016-10-10 22:07:18 UTC
view on stackexchange narkive permalink

millis () używa timera0 (połączonego z zegarem procesora) do zliczania czasu, ale tryb ADC_sleep zatrzymuje zegar procesora, dlatego millis () będzie dryfować (opóźniać się) po każdej konwersji ADC wykonanej w trybie ADC_sleep.

Ze standardową liczbą cykli procesora potrzebnych do konwersji ADC (prescaler ADC = 128 pomnożone przez cykle zegara ADC = 13) i ze standardowym preskalerem dla timera0 (64), wyglądałoby na to, że millis () traci 13,5 tiki ( około 0,1 ms) na każdą konwersję ADC wykonywaną w trybie ADC_sleep.

Mógłbym zaktualizować rejestr timer0 (TCNT0), zwiększając go ręcznie o 14, ale przede wszystkim nadal miałbym dryf 0,5 tyknięcia (lub nieco mniej, ponieważ operacja aktualizacji zajęłaby kilka cykli), po drugie przegapiłbym zdarzenie przepełnienia (to, którego Arduino używa wewnętrznie do śledzenia czasu) za każdym razem, gdy konwersja ADC jest rozpoczynana z TCNT0> 230.

Jak czy powinienem przejść do trybu ADC_sleep podczas konwersji ADC, ale nadal zachować dokładność millis ()?

Czy to dotyczy SLEEP_MODE_ADC? A na jakim AVR / Arduino? Możliwym rozwiązaniem jest użycie Timer2 dla Arduino millis-ISR. Zobacz przykład Cosa RTT; https://github.com/mikaelpatel/Cosa/blob/master/cores/cosa/Cosa/RTT.hh (i pliki RTT.cpp, RTT_Config.hh)
Cztery odpowiedzi:
James Waldby - jwpat7
2016-10-10 23:24:22 UTC
view on stackexchange narkive permalink

Zamiast próbować aktualizować TCNT0, lepiej byłoby śledzić liczbę cykli utraconych w wyniku konwersji ADC i w procedurze pośredniej - np. omillis () - skompensować te cykle. [Edytuj: zobacz lepszą alternatywę poniżej]

Bardziej szczegółowo:

• Przy każdej konwersji dodaj około 128 · 13 cykli (lub być może 128 · 13.5, aby uwzględnić średnie opóźnienie preskalera) do licznika cykli:
dawno utracone cykle; ...; lostcycles + = ADCcycles;

• W ramach każdego wywołania omillis () powiedz return millis () + lostcycles / 16000

• Albo powiedz return millis () + lostcycles>>14 , aby uniknąć marnowania czasu na podział. W przypadku korekty byłoby to o 2% mniejsze, ale zakładam, że korekta jest niewielka, powiedzmy 5% całkowitego czasu, co prowadzi do około 0,1% błędu z powodu użycia przesunięcia zamiast dzielenia.

Re: „Przegapiłbym zdarzenie przepełnienia (tego, którego Arduino używa wewnętrznie do śledzenia czasu) za każdym razem, gdy konwersja ADC jest rozpoczynana z TCNT0> 230”

To nieprawidłowe [jeśli używasz sugerowanego podejście]. Nawet jeśli przepełnienie timera 0 zostanie obsłużone sto mikrosekund z opóźnieniem, zostanie obsłużone poprawnie. [Jak zauważono w komentarzu Edgara Boneta, metoda, która po prostu dodaje do TCNT0 bez sprawdzania przepełnienia arytmetycznego, jak w oryginalnym podejściu, może przeoczyć takt timera.]

Uwaga, przykładowy kod wykorzystujący SLEEP_MODE_ADC w sekcji „Uśpij podczas czytania” w sekcji Konwersja ADC na Arduino na forum Gammon sugeruje, że należy sprawdzić przypadek, w którym występuje licznik czasu lub inne przerwanie podczas konwersji ADC. Podczas gdy SLEEP_MODE_ADC zatrzymuje clk I / O , clk CPU i clk FLASH , opuszcza Timer 2, WDT, i kilka innych potencjalnych aktywnych źródeł przerwań.

Lepsza alternatywa:

Jak zauważa Gerben, nieco czystszym rozwiązaniem jest aktualizacja zmiennej count obsługiwanej przez millis () , zamiast używania procedury pośredniej. Więcej informacji znajdziesz w jego odpowiedzi na wcześniejsze pytanie. Oto adaptacja [nieprzetestowana] pomysłu do obecnego przypadku:

  unsigned int lostcycles = 0; ...; void accountForADC () {enum {ADCcycles = 13 * 128 + 64; ms_cycles = 16000}; // dostosuj odpowiednio zewnętrzny volatile unsigned long timer0_millis; zgubione motocykle + = ADCycles; while (lostcycles > = ms_cycles) {// Ta pętla zwykle wykonuje tylko jeden przebieg bajtów statusReg = SREG; // Zapisz stan przerwania cli (); // Wyłącz przerwania ++ timer0_millis; // Potrzebujesz przerwania dla atomowości SREG = statusReg; // Przywróć stary status lostcycles - = ms_cycles; }}  

Po uruchomieniu, przy dziesiątym wywołaniu funkcji accountForADC () ten kod doda 1 do timer0_millis , aby uwzględnić milisekundę przegrane z konwersjami ADC. lostcycles - = ms_cycles ustawi lostcycles na 1280. Po kolejnych 9 rozmowach ponownie dodajemy 1 do timer0_millis i zmniejszamy liczbę lostcycles ; i tak dalej.

Na koniec moim pomysłem było również śledzenie liczby konwersji, dziękuję za potwierdzenie (twój jest czystszy niż mój)
@OlafM, dzięki! Uwaga, źle obliczyłem liczbę cykli, aby dodać - 108 nas to złe jednostki; Zakładam, że 13 * 128 = 1664 cykli to liczba, której należy użyć
Zdecydowanie możesz przegapić zdarzenie przepełnienia, jeśli, zgodnie z pierwotnym pomysłem PO, zrobisz `TCNT0 + = 26;` i _ ta_ operacja przepełnia się.
@EdgarBonet, prawda! Coś, o czym ostrzega karta danych.
Mój pomysł odnosił się do tego, który miałem po napisaniu, to znaczy odjąć 0,1 ms razy liczbę konwersji. Ten śledzący konwersje * 1664/16000 jest dokładniejszy.
@OlafM: O ile nie zresetujesz preskalera, konwersja ADC zajmie (1664 + nieznana liczba z zakresu od 0 do 127) cykli procesora z ADSC do ADIF. C.f. wykresy czasowe w sekcji 24.4 arkusza danych.
Jeśli nie chcesz zmieniać wszystkich wywołań „millis”, możesz w rzeczywistości zwiększyć zmienną używaną przez „millis”. Spójrz na [dół mojej odpowiedzi tutaj] (http://arduino.stackexchange.com/a/22997/2881).
@Gerben, dzięki; Pod koniec odpowiedzi zredagowałem to podejście
@jwpat7, ponieważ `timer0_millis` ma długość 4 bajtów, który jest aktualizowany w ISR, musisz zaktualizować go atomowo lub mogą się zdarzyć dziwne rzeczy, jeśli przerwanie timer0 nastąpi w środku twojego kodu zmieniając 4 bajty. W moim kodzie tymczasowo wyłączyłem przerwania.
@Gerben, dzięki za zwrócenie uwagi ... odpowiednio zredagowałem odpowiedź
Edgar Bonet
2016-10-10 23:14:30 UTC
view on stackexchange narkive permalink

Nie powiem ci, jak zrobić to, o co prosisz, ponieważ tak naprawdę nie wiem, jak to zrobić. Ale mam przeczucie, że da się zrobić. Tylko że byłoby to naprawdę, bardzo trudne , więc pojawia się pytanie: czy warto?

Aby uniknąć publikowania odpowiedzi opartych wyłącznie na opiniach, postaram się udzielić you someleads:

  • Możesz ustawić timer 0 z rozdzielczością pojedynczego cyklu procesora, po prostu resetując jego prescaler za każdym razem, gdy ustawisz nową wartość.
  • Możesz wprowadzać bardzo precyzyjne opóźnienia z funkcją _delay_us () . Argument powinien być stałą czasu kompilacji. _delay_us (0.125) robi dokładnie to, co mówi, że robi.
  • Uważaj na preskaler zegara ADC: będzie on powodował pewne wahania czasu ADC, chyba że go zresetujesz. AFAIK, jedynym sposobem na zresetowanie tego preskalera jest rozpoczęcie konwersji ADC przy użyciu jakiegoś źródła wyzwalania w trybie automatycznego wyzwalania. Może to kosztować licznik czasu.
  • Jeśli przegapisz zdarzenie przepełnienia, możesz po prostu ręcznie wywołać odpowiedni ISR ​​( TIMER0_OVF_vect () , który deklarujesz za pomocą ISR (TIMER0_OVF_vect ); ). Brzmi głupio, ale działa, pod warunkiem, że na siłę włącza przerwania po powrocie.

Aby dowiedzieć się, ile tików dokładnie tracisz, możesz wydać fewevenings uważnie przestudiuj czasy w arkuszu danych lub możesz spróbować skalibrować je na stabilnym źródle częstotliwości. Poleciłbym to drugie.

Gerben
2016-10-11 01:24:51 UTC
view on stackexchange narkive permalink

Myślę, że źle przeczytałeś arkusz danych. Konwersja trwa 13 cykli ADC (z wyjątkiem pierwszego), a nie 13,5.

Po drugie; Konwersja zaczyna się przy następnej narastającej krawędzi zegara ADC, a nie bezpośrednio. Tak więc, ponieważ preskaler ma wartość 128, może zająć dodatkowe 127 cykli zegara głównego przed narastającym zboczem kolejnego zegara ADC. Oznacza to, że konwersja ADC trwa od 13 do 13,9921875 cykli zegara ADC.

Nie jestem też pewien, ile czasu zajmie uC ponowne uruchomienie zegara procesora. Mogę więc „stracić” więcej czasu.

Chociaż musisz być świadomy, że oscylator kwarcowy nie jest tak dokładny na początku.

Wydawało mi się, że zegar procesora zatrzyma się, gdy ADC faktycznie się uruchomi, dlatego nie uwzględniono cykli opóźnienia „od 0 do 127”. Widzę, że to bardziej złożone.
Nick Gammon
2016-10-11 04:43:07 UTC
view on stackexchange narkive permalink

Czas rozpoczęcia konwersji nie jest ustalony. Na mojej stronie poświęconej licznikom czasu widzimy następujące informacje:

Porada:”

Jest dodatkowy czas przed rozpoczyna się konwersja. Konwersja rozpoczyna się na krawędzi czołowej zegara ADC, a nie w momencie, gdy prosi o to kod. W przypadku skalera 128, może być dodanych 127 dodatkowych cykli zegara (procesora), ponieważ sprzęt musi czekać na następny cykl zegara ADC. Może to dodać 7,938 µs do czasu konwersji.

Wygląda na to, że preskaler można zresetować, wpisując 0 do ADEN. Zgodnie z arkuszem danych:

Prescaler rozpoczyna odliczanie od momentu włączenia ADC poprzez ustawienie bitu ADEN w ADCSRA. Prescaler działa tak długo, jak ustawiony jest bit ADEN i jest stale resetowany, gdy ADEN jest niski.

Jeśli jednak to zrobisz, dodasz do czasu konwersji, tak jak będzie teraz weź 25 cykli zegara ADC zamiast 13.


  • Jak sugerowali inni, jeśli dokładny czas jest tym, czego potrzebujesz, to zewnętrzny układ zegara może być dobrym rozwiązaniem. Chip i części prawdopodobnie osiągną cenę mniej więcej jednego dolara.

  • Alternatywą byłoby uzyskanie zewnętrznego układu ADC, przenosząc w ten sposób problem redukcji szumów na chip który (miejmy nadzieję) został zaprojektowany, aby sobie z tym poradzić.

  • Inną możliwością byłoby podłączenie wejścia zewnętrznego zegara do Timera 1 i uruchomienie go asynchronicznie. Jak czytałem arkusz danych, ten zegar będzie działał w trybie uśpienia ADC, więc możesz go użyć do pomiaru czasu. (To nie pomogłoby w przypadku millis () i micros (), ale można by napisać coś podobnego do Timera 1).

    Edytuj - jak wskazał Edgar Bonet się, źle odczytałem arkusz danych. To Timer 2 może to zrobić, a nie Timer 1. A szpilki do asynchronicznego uruchamiania timera nie znajdują się na chipie PDIP w Uno i nie są wyprowadzone do pinów płytki w Mega.

Tylko Timer 2 może działać asynchronicznie, przynajmniej na Uno i Mega. I nie możesz używać trybu asynchronicznego na tych płytach, ponieważ wymagane piny (TOSC1 / TOSC2) nie są dostępne.
Ups! Błędnie przeczytać arkusz danych. Poprawiłem moją odpowiedź, dzięki.


To pytanie i odpowiedź zostało automatycznie przetłumaczone z języka angielskiego.Oryginalna treść jest dostępna na stackexchange, za co dziękujemy za licencję cc by-sa 3.0, w ramach której jest rozpowszechniana.
Loading...