Funkcja millis
działałaby w zakresie 100+ mikrosekund lub mniej. Czy istnieje wiarygodny sposób na zmierzenie czasu potrzebnego na pojedyncze połączenie Millis?
Jednym z podejść, które przychodzi na myśl, jest micros
jednak użycie wywołania do, micros
który obejmie również czas wywołania funkcji micros
, więc w zależności od tego, jak długo trwa mikros, pomiar dla millis
może być wyłączony.
Muszę to znaleźć, ponieważ aplikacja, nad którą pracuję, wymaga dokładnych pomiarów czasu dla każdego kroku w kodzie, w tym millis
.
miilis
trwa połączenie .Odpowiedzi:
Jeśli chcesz dokładnie wiedzieć , ile czasu to zajmie, istnieje tylko jedno rozwiązanie: spójrz na demontaż!
Począwszy od minimalnego kodu:
Ten kod skompilowany, a następnie wprowadzony,
avr-objdump -S
tworzy udokumentowany demontaż. Oto interesujące fragmenty:void loop()
produkuje:Który jest funkcją call (
call
), cztery kopie (które kopiują każdy z bajtów wuint32_t
wartości zwracanej przezmillis()
(zauważ, że dokumenty arduino nazywają to along
, ale są niepoprawne, aby nie określały wyraźnie wielkości zmiennych)) i na koniec funkcja return.call
wymaga 4 cykli zegara, a każdysts
wymaga 2 cykli zegara, więc mamy minimum 12 cykli zegara tylko narzut wywołania funkcji.Teraz spójrzmy na demontaż
<millis>
funkcji, która znajduje się w0x14e
:Jak widać,
millis()
funkcja jest dość prosta:in
zapisuje ustawienia rejestru przerwań (1 cykl)cli
wyłącza przerwania (1 cykl)lds
skopiuj jeden z 4 bajtów bieżącej wartości licznika milli do rejestru tymczasowego (2 cykle zegara)lds
Bajt 2 (2 cykle zegara)lds
Bajt 3 (2 cykle zegara)lds
Bajt 4 (2 cykle zegara)out
przywróć ustawienia przerwań (1 cykl zegara)movw
losuj rejestruje się wokół (1 cykl zegara)movw
i ponownie (1 cykl zegara)ret
powrót z podprogramu (4 cykle)Tak więc, jeśli dodamy je wszystkie, mamy w sumie 17 cykli zegara w
millis()
samej funkcji, plus narzut wywołania wynoszący 12, co daje w sumie 29 cykli zegara.Przy założeniu częstotliwości taktowania 16 MHz (większość arduinos), każdy cykl zegara to
1 / 16e6
sekundy lub 0,0000000625 sekundy, czyli 62,5 nanosekundy. 62,5 ns * 29 = 1.812 mikrosekund.Dlatego całkowity czas wykonania pojedynczego
millis()
połączenia w większości Arduinos wyniesie 1,812 mikrosekundy .Odniesienie do zgromadzenia AVR
Na marginesie, jest tu miejsce na optymalizację! Jeśli zaktualizujesz
unsigned long millis(){}
definicję funkcjiinline unsigned long millis(){}
, usuniesz narzut wywołania (kosztem nieco większego rozmiaru kodu). Ponadto wygląda na to, że kompilator wykonuje dwa niepotrzebne ruchy (dwamovw
wywołania, ale nie przyjrzałem się temu zbyt dokładnie).Naprawdę, biorąc pod uwagę obciążenie wywołania funkcji jest 5 instrukcje, a rzeczywiste zawartość tej
millis()
funkcji jest tylko 6 instrukcje, myślę, żemillis()
funkcja powinna być naprawdęinline
domyślnie, ale Arduino codebase jest raczej słabo zoptymalizowany.Oto pełna dezasemblacja dla wszystkich zainteresowanych:
źródło
sts
nie powinna być liczona jako koszt połączenia: jest to koszt przechowywania wyniku w zmiennej zmiennej, czego normalnie byś nie zrobił. 2) W moim systemie (Arduino 1.0.5, gcc 4.8.2) nie mammovw
s. Zatem koszt połączeniamillis()
wynosi: 4 cykle narzutu połączenia + 15 cykli wmillis()
sobie = 19 cykli łącznie (≈ 1,188 µs @ 16 MHz).x
jestuint16_t
. Powinny to być maksymalnie 2 kopie, jeśli jest to przyczyną. W każdym razie pytaniem jest, ile czasumillis()
zajmuje użycie , a nie wywołanie, ignorując wynik. Ponieważ każde praktyczne zastosowanie będzie wymagało zrobienia czegoś z wynikiem, wymusiłem przechowywanie wyniku przezvolatile
. Zwykle ten sam efekt zostałby osiągnięty przez późniejsze użycie zmiennej, która jest ustawiona na wartość zwracaną wywołania, ale nie chciałem, aby dodatkowe wywołanie zajmowało miejsce w odpowiedzi.uint16_t
w źródle nie pasuje do zestawu (4 bajty zapisane w pamięci RAM). Prawdopodobnie opublikowałeś źródło i demontaż dwóch różnych wersji.Napisz szkic, który millis 1000 razy, nie tworząc pętli, ale kopiując i wklejając. Zmierz to i porównaj z faktycznym oczekiwanym czasem. Pamiętaj, że wyniki mogą się różnić w zależności od wersji IDE (w szczególności jej kompilatora).
Inną opcją jest przełączanie pinów We / Wy przed i po wywołaniu millis, a następnie zmierzenie czasu dla bardzo małej wartości i nieco większej wartości. Porównaj zmierzone czasy i oblicz koszty ogólne.
Najdokładniejszym sposobem jest spojrzenie na listę dezasemblacji, wygenerowany kod. Ale to nie jest dla osób o słabym sercu. Musisz dokładnie przestudiować arkusz danych, jak długo trwa każdy cykl instrukcji.
źródło
millis()
połączeń?delay
, masz rację. Ale pomysł pozostaje ten sam, możesz zaplanować dużą liczbę połączeń i uśrednić je. Globalne wyłączanie przerwań może nie być dobrym pomysłem; o)Po raz drugi dzwonię do Millis wielokrotnie, a następnie porównuję rzeczywiste z oczekiwanymi.
Obciążenie będzie minimalne, ale będzie miało znaczenie, im więcej razy wywołasz millis ().
Jeśli spojrzysz na
Widać, że millis () jest bardzo mały przy zaledwie 4 instrukcjach
(cli is simply # define cli() \__asm__ \__volatile__ ("cli" ::))
i zwrocie.Nazwałbym to około 10 milionów razy przy użyciu pętli FOR, która ma zmienność warunkową. Zmienne słowo kluczowe uniemożliwi kompilatorowi podjęcie próby optymalizacji w samej pętli.
Nie gwarantuję, że poniższe elementy są syntaktycznie idealne ..
zgaduję, że potrzeba około 900 ms lub około 56us na połączenie do millis. (Nie mam poręcznego bankomatu z aruduino.
źródło
int temp1,temp2;
na,volatile int temp1,temp2;
aby zapobiec potencjalnej optymalizacji kompilatora.