Apakah mungkin untuk menemukan waktu yang diambil oleh millis?

13

Fungsi millisakan berjalan dalam rentang 100+ mikrodetik atau kurang. Apakah ada cara yang dapat diandalkan untuk mengukur waktu yang diambil dengan satu panggilan millis?

Salah satu pendekatan yang muncul dalam pikiran adalah menggunakan micros, bagaimanapun, panggilan untuk microsakan mencakup waktu yang diambil oleh panggilan fungsi micrositu sendiri juga, jadi tergantung pada berapa lama mik, pengukuran untuk millismungkin tidak aktif.

Saya perlu menemukan ini sebagai aplikasi yang saya kerjakan membutuhkan pengukuran waktu yang akurat untuk setiap langkah yang diambil dalam kode, termasuk millis.

asheeshr
sumber
Bisakah Anda mengklarifikasi apa yang Anda minta di sini? Apakah Anda mencoba mendapatkan waktu yang akurat dari millis () atau Anda mencoba mengetahui berapa lama untuk memanggil fungsi millis ()?
Cybergibbons
@Cybergibbons Berapa lama panggilan miilisberlangsung.
asheeshr

Jawaban:

21

Jika Anda ingin tahu persis berapa lama sesuatu akan terjadi, hanya ada satu solusi: Lihatlah pembongkaran!

Dimulai dengan kode minimal:

void setup(){};

volatile uint16_t x;
void loop()
{
  x = millis();

}

Kode ini dikompilasi dan kemudian dimasukkan ke dalam avr-objdump -Smenghasilkan pembongkaran yang didokumentasikan. Berikut petikannya yang menarik:

void loop() menghasilkan:

000000a8 <loop>:
  a8:   0e 94 a7 00     call    0x14e   ; 0x14e <millis>
  ac:   60 93 00 01     sts 0x0100, r22
  b0:   70 93 01 01     sts 0x0101, r23
  b4:   80 93 02 01     sts 0x0102, r24
  b8:   90 93 03 01     sts 0x0103, r25
  bc:   08 95           ret

Yang merupakan panggilan fungsi ( call), empat salinan (yang menyalin masing-masing byte dalam nilai uint32_tpengembalian millis()(perhatikan bahwa dokumen arduino menyebutnya sebagai a long, tetapi tidak benar untuk tidak secara eksplisit menentukan ukuran variabel)), dan akhirnya fungsi kembali.

callmembutuhkan 4 siklus clock, dan masing-masing stsmembutuhkan 2 siklus clock, jadi kami memiliki minimal 12 siklus clock hanya untuk panggilan fungsi overhead.

Sekarang, mari kita lihat pembongkaran <millis>fungsi, yang terletak di 0x14e:

unsigned long millis()
{
    unsigned long m;
    uint8_t oldSREG = SREG;
 14e:   8f b7           in  r24, 0x3f   ; 63

    // 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();
 150:   f8 94           cli
    m = timer0_millis;
 152:   20 91 08 01     lds r18, 0x0108
 156:   30 91 09 01     lds r19, 0x0109
 15a:   40 91 0a 01     lds r20, 0x010A
 15e:   50 91 0b 01     lds r21, 0x010B
    SREG = oldSREG;
 162:   8f bf           out 0x3f, r24   ; 63

    return m;
}
 164:   b9 01           movw    r22, r18
 166:   ca 01           movw    r24, r20
 168:   08 95           ret

Seperti yang Anda lihat, millis()fungsinya cukup sederhana:

  1. in menyimpan pengaturan register interupsi (1 siklus)
  2. cli mematikan interupsi (1 siklus)
  3. lds salin salah satu dari 4 byte dari nilai saat ini dari penghitung mili ke dalam register sementara (2 siklus clock)
  4. lds Byte 2 (2 siklus jam)
  5. lds Byte 3 (2 siklus jam)
  6. lds Byte 4 (2 siklus jam)
  7. out pulihkan pengaturan interupsi (1 siklus jam)
  8. movw register acak sekitar (siklus 1 jam)
  9. movw dan lagi (1 clock cycle)
  10. ret kembali dari subrutin (4 siklus)

Jadi, jika kita menambahkan semuanya, kita memiliki total 17 siklus clock dalam millis()fungsi itu sendiri, ditambah panggilan overhead 12, dengan total 29 siklus clock.

Dengan asumsi clock rate 16 Mhz (kebanyakan arduinos), setiap siklus clock adalah 1 / 16e6detik, atau 0,0000000625 detik, yaitu 62,5 nanoseconds. 62,5 ns * 29 = 1,812 mikrodetik.

Oleh karena itu, total waktu eksekusi untuk satu millis()panggilan di sebagian besar Arduino adalah 1.812 mikrodetik .


Referensi Majelis AVR

Sebagai catatan tambahan, ada ruang untuk optimasi di sini! Jika Anda memperbarui unsigned long millis(){}definisi fungsi menjadi inline unsigned long millis(){}, Anda akan menghapus overhead panggilan (dengan biaya ukuran kode yang sedikit lebih besar). Lebih jauh lagi, sepertinya kompiler sedang melakukan dua gerakan yang tidak perlu (dua movwpanggilan, tapi saya belum melihatnya dari dekat).

Sungguh, mengingat overhead panggilan fungsi adalah 5 instruksi, dan isi sebenarnya dari millis()fungsi hanya 6 instruksi, saya pikir millis()fungsi harus benar-benar secara inlinedefault, tetapi basis kode Arduino agak dioptimalkan dengan buruk.


Inilah pembongkaran penuh bagi siapa pun yang tertarik:

sketch_feb13a.cpp.elf:     file format elf32-avr


Disassembly of section .text:

00000000 <__vectors>:
    SREG = oldSREG;

    return m;
}

unsigned long micros() {
   0:   0c 94 34 00     jmp 0x68    ; 0x68 <__ctors_end>
   4:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
   8:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
   c:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  10:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  14:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  18:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  1c:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  20:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  24:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  28:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  2c:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  30:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  34:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  38:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  3c:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  40:   0c 94 5f 00     jmp 0xbe    ; 0xbe <__vector_16>
  44:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  48:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  4c:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  50:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  54:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  58:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  5c:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  60:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  64:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>

00000068 <__ctors_end>:
  68:   11 24           eor r1, r1
  6a:   1f be           out 0x3f, r1    ; 63
  6c:   cf ef           ldi r28, 0xFF   ; 255
  6e:   d8 e0           ldi r29, 0x08   ; 8
  70:   de bf           out 0x3e, r29   ; 62
  72:   cd bf           out 0x3d, r28   ; 61

00000074 <__do_copy_data>:
  74:   11 e0           ldi r17, 0x01   ; 1
  76:   a0 e0           ldi r26, 0x00   ; 0
  78:   b1 e0           ldi r27, 0x01   ; 1
  7a:   e2 e0           ldi r30, 0x02   ; 2
  7c:   f2 e0           ldi r31, 0x02   ; 2
  7e:   02 c0           rjmp    .+4         ; 0x84 <.do_copy_data_start>

00000080 <.do_copy_data_loop>:
  80:   05 90           lpm r0, Z+
  82:   0d 92           st  X+, r0

00000084 <.do_copy_data_start>:
  84:   a0 30           cpi r26, 0x00   ; 0
  86:   b1 07           cpc r27, r17
  88:   d9 f7           brne    .-10        ; 0x80 <.do_copy_data_loop>

0000008a <__do_clear_bss>:
  8a:   11 e0           ldi r17, 0x01   ; 1
  8c:   a0 e0           ldi r26, 0x00   ; 0
  8e:   b1 e0           ldi r27, 0x01   ; 1
  90:   01 c0           rjmp    .+2         ; 0x94 <.do_clear_bss_start>

00000092 <.do_clear_bss_loop>:
  92:   1d 92           st  X+, r1

00000094 <.do_clear_bss_start>:
  94:   ad 30           cpi r26, 0x0D   ; 13
  96:   b1 07           cpc r27, r17
  98:   e1 f7           brne    .-8         ; 0x92 <.do_clear_bss_loop>
  9a:   0e 94 f0 00     call    0x1e0   ; 0x1e0 <main>
  9e:   0c 94 ff 00     jmp 0x1fe   ; 0x1fe <_exit>

000000a2 <__bad_interrupt>:
  a2:   0c 94 00 00     jmp 0   ; 0x0 <__vectors>

000000a6 <setup>:
  a6:   08 95           ret

000000a8 <loop>:
  a8:   0e 94 a7 00     call    0x14e   ; 0x14e <millis>
  ac:   60 93 00 01     sts 0x0100, r22
  b0:   70 93 01 01     sts 0x0101, r23
  b4:   80 93 02 01     sts 0x0102, r24
  b8:   90 93 03 01     sts 0x0103, r25
  bc:   08 95           ret

000000be <__vector_16>:
#if defined(__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
ISR(TIM0_OVF_vect)
#else
ISR(TIMER0_OVF_vect)
#endif
{
  be:   1f 92           push    r1
  c0:   0f 92           push    r0
  c2:   0f b6           in  r0, 0x3f    ; 63
  c4:   0f 92           push    r0
  c6:   11 24           eor r1, r1
  c8:   2f 93           push    r18
  ca:   3f 93           push    r19
  cc:   8f 93           push    r24
  ce:   9f 93           push    r25
  d0:   af 93           push    r26
  d2:   bf 93           push    r27
    // copy these to local variables so they can be stored in registers
    // (volatile variables must be read from memory on every access)
    unsigned long m = timer0_millis;
  d4:   80 91 08 01     lds r24, 0x0108
  d8:   90 91 09 01     lds r25, 0x0109
  dc:   a0 91 0a 01     lds r26, 0x010A
  e0:   b0 91 0b 01     lds r27, 0x010B
    unsigned char f = timer0_fract;
  e4:   30 91 0c 01     lds r19, 0x010C

    m += MILLIS_INC;
  e8:   01 96           adiw    r24, 0x01   ; 1
  ea:   a1 1d           adc r26, r1
  ec:   b1 1d           adc r27, r1
    f += FRACT_INC;
  ee:   23 2f           mov r18, r19
  f0:   2d 5f           subi    r18, 0xFD   ; 253
    if (f >= FRACT_MAX) {
  f2:   2d 37           cpi r18, 0x7D   ; 125
  f4:   20 f0           brcs    .+8         ; 0xfe <__vector_16+0x40>
        f -= FRACT_MAX;
  f6:   2d 57           subi    r18, 0x7D   ; 125
        m += 1;
  f8:   01 96           adiw    r24, 0x01   ; 1
  fa:   a1 1d           adc r26, r1
  fc:   b1 1d           adc r27, r1
    }

    timer0_fract = f;
  fe:   20 93 0c 01     sts 0x010C, r18
    timer0_millis = m;
 102:   80 93 08 01     sts 0x0108, r24
 106:   90 93 09 01     sts 0x0109, r25
 10a:   a0 93 0a 01     sts 0x010A, r26
 10e:   b0 93 0b 01     sts 0x010B, r27
    timer0_overflow_count++;
 112:   80 91 04 01     lds r24, 0x0104
 116:   90 91 05 01     lds r25, 0x0105
 11a:   a0 91 06 01     lds r26, 0x0106
 11e:   b0 91 07 01     lds r27, 0x0107
 122:   01 96           adiw    r24, 0x01   ; 1
 124:   a1 1d           adc r26, r1
 126:   b1 1d           adc r27, r1
 128:   80 93 04 01     sts 0x0104, r24
 12c:   90 93 05 01     sts 0x0105, r25
 130:   a0 93 06 01     sts 0x0106, r26
 134:   b0 93 07 01     sts 0x0107, r27
}
 138:   bf 91           pop r27
 13a:   af 91           pop r26
 13c:   9f 91           pop r25
 13e:   8f 91           pop r24
 140:   3f 91           pop r19
 142:   2f 91           pop r18
 144:   0f 90           pop r0
 146:   0f be           out 0x3f, r0    ; 63
 148:   0f 90           pop r0
 14a:   1f 90           pop r1
 14c:   18 95           reti

0000014e <millis>:

unsigned long millis()
{
    unsigned long m;
    uint8_t oldSREG = SREG;
 14e:   8f b7           in  r24, 0x3f   ; 63

    // 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();
 150:   f8 94           cli
    m = timer0_millis;
 152:   20 91 08 01     lds r18, 0x0108
 156:   30 91 09 01     lds r19, 0x0109
 15a:   40 91 0a 01     lds r20, 0x010A
 15e:   50 91 0b 01     lds r21, 0x010B
    SREG = oldSREG;
 162:   8f bf           out 0x3f, r24   ; 63

    return m;
}
 164:   b9 01           movw    r22, r18
 166:   ca 01           movw    r24, r20
 168:   08 95           ret

0000016a <init>:

void init()
{
    // this needs to be called before setup() or some functions won't
    // work there
    sei();
 16a:   78 94           sei

    // on the ATmega168, timer 0 is also used for fast hardware pwm
    // (using phase-correct PWM would mean that timer 0 overflowed half as often
    // resulting in different millis() behavior on the ATmega8 and ATmega168)
#if defined(TCCR0A) && defined(WGM01)
    sbi(TCCR0A, WGM01);
 16c:   84 b5           in  r24, 0x24   ; 36
 16e:   82 60           ori r24, 0x02   ; 2
 170:   84 bd           out 0x24, r24   ; 36
    sbi(TCCR0A, WGM00);
 172:   84 b5           in  r24, 0x24   ; 36
 174:   81 60           ori r24, 0x01   ; 1
 176:   84 bd           out 0x24, r24   ; 36
    // this combination is for the standard atmega8
    sbi(TCCR0, CS01);
    sbi(TCCR0, CS00);
#elif defined(TCCR0B) && defined(CS01) && defined(CS00)
    // this combination is for the standard 168/328/1280/2560
    sbi(TCCR0B, CS01);
 178:   85 b5           in  r24, 0x25   ; 37
 17a:   82 60           ori r24, 0x02   ; 2
 17c:   85 bd           out 0x25, r24   ; 37
    sbi(TCCR0B, CS00);
 17e:   85 b5           in  r24, 0x25   ; 37
 180:   81 60           ori r24, 0x01   ; 1
 182:   85 bd           out 0x25, r24   ; 37

    // enable timer 0 overflow interrupt
#if defined(TIMSK) && defined(TOIE0)
    sbi(TIMSK, TOIE0);
#elif defined(TIMSK0) && defined(TOIE0)
    sbi(TIMSK0, TOIE0);
 184:   ee e6           ldi r30, 0x6E   ; 110
 186:   f0 e0           ldi r31, 0x00   ; 0
 188:   80 81           ld  r24, Z
 18a:   81 60           ori r24, 0x01   ; 1
 18c:   80 83           st  Z, r24
    // this is better for motors as it ensures an even waveform
    // note, however, that fast pwm mode can achieve a frequency of up
    // 8 MHz (with a 16 MHz clock) at 50% duty cycle

#if defined(TCCR1B) && defined(CS11) && defined(CS10)
    TCCR1B = 0;
 18e:   e1 e8           ldi r30, 0x81   ; 129
 190:   f0 e0           ldi r31, 0x00   ; 0
 192:   10 82           st  Z, r1

    // set timer 1 prescale factor to 64
    sbi(TCCR1B, CS11);
 194:   80 81           ld  r24, Z
 196:   82 60           ori r24, 0x02   ; 2
 198:   80 83           st  Z, r24
#if F_CPU >= 8000000L
    sbi(TCCR1B, CS10);
 19a:   80 81           ld  r24, Z
 19c:   81 60           ori r24, 0x01   ; 1
 19e:   80 83           st  Z, r24
    sbi(TCCR1, CS10);
#endif
#endif
    // put timer 1 in 8-bit phase correct pwm mode
#if defined(TCCR1A) && defined(WGM10)
    sbi(TCCR1A, WGM10);
 1a0:   e0 e8           ldi r30, 0x80   ; 128
 1a2:   f0 e0           ldi r31, 0x00   ; 0
 1a4:   80 81           ld  r24, Z
 1a6:   81 60           ori r24, 0x01   ; 1
 1a8:   80 83           st  Z, r24

    // set timer 2 prescale factor to 64
#if defined(TCCR2) && defined(CS22)
    sbi(TCCR2, CS22);
#elif defined(TCCR2B) && defined(CS22)
    sbi(TCCR2B, CS22);
 1aa:   e1 eb           ldi r30, 0xB1   ; 177
 1ac:   f0 e0           ldi r31, 0x00   ; 0
 1ae:   80 81           ld  r24, Z
 1b0:   84 60           ori r24, 0x04   ; 4
 1b2:   80 83           st  Z, r24

    // configure timer 2 for phase correct pwm (8-bit)
#if defined(TCCR2) && defined(WGM20)
    sbi(TCCR2, WGM20);
#elif defined(TCCR2A) && defined(WGM20)
    sbi(TCCR2A, WGM20);
 1b4:   e0 eb           ldi r30, 0xB0   ; 176
 1b6:   f0 e0           ldi r31, 0x00   ; 0
 1b8:   80 81           ld  r24, Z
 1ba:   81 60           ori r24, 0x01   ; 1
 1bc:   80 83           st  Z, r24
#if defined(ADCSRA)
    // set a2d prescale factor to 128
    // 16 MHz / 128 = 125 KHz, inside the desired 50-200 KHz range.
    // XXX: this will not work properly for other clock speeds, and
    // this code should use F_CPU to determine the prescale factor.
    sbi(ADCSRA, ADPS2);
 1be:   ea e7           ldi r30, 0x7A   ; 122
 1c0:   f0 e0           ldi r31, 0x00   ; 0
 1c2:   80 81           ld  r24, Z
 1c4:   84 60           ori r24, 0x04   ; 4
 1c6:   80 83           st  Z, r24
    sbi(ADCSRA, ADPS1);
 1c8:   80 81           ld  r24, Z
 1ca:   82 60           ori r24, 0x02   ; 2
 1cc:   80 83           st  Z, r24
    sbi(ADCSRA, ADPS0);
 1ce:   80 81           ld  r24, Z
 1d0:   81 60           ori r24, 0x01   ; 1
 1d2:   80 83           st  Z, r24

    // enable a2d conversions
    sbi(ADCSRA, ADEN);
 1d4:   80 81           ld  r24, Z
 1d6:   80 68           ori r24, 0x80   ; 128
 1d8:   80 83           st  Z, r24
    // here so they can be used as normal digital i/o; they will be
    // reconnected in Serial.begin()
#if defined(UCSRB)
    UCSRB = 0;
#elif defined(UCSR0B)
    UCSR0B = 0;
 1da:   10 92 c1 00     sts 0x00C1, r1
#endif
}
 1de:   08 95           ret

000001e0 <main>:
#include <Arduino.h>

int main(void)
 1e0:   cf 93           push    r28
 1e2:   df 93           push    r29
{
    init();
 1e4:   0e 94 b5 00     call    0x16a   ; 0x16a <init>

#if defined(USBCON)
    USBDevice.attach();
#endif

    setup();
 1e8:   0e 94 53 00     call    0xa6    ; 0xa6 <setup>

    for (;;) {
        loop();
        if (serialEventRun) serialEventRun();
 1ec:   c0 e0           ldi r28, 0x00   ; 0
 1ee:   d0 e0           ldi r29, 0x00   ; 0
#endif

    setup();

    for (;;) {
        loop();
 1f0:   0e 94 54 00     call    0xa8    ; 0xa8 <loop>
        if (serialEventRun) serialEventRun();
 1f4:   20 97           sbiw    r28, 0x00   ; 0
 1f6:   e1 f3           breq    .-8         ; 0x1f0 <main+0x10>
 1f8:   0e 94 00 00     call    0   ; 0x0 <__vectors>
 1fc:   f9 cf           rjmp    .-14        ; 0x1f0 <main+0x10>

000001fe <_exit>:
 1fe:   f8 94           cli

00000200 <__stop_program>:
 200:   ff cf           rjmp    .-2         ; 0x200 <__stop_program>
Connor Wolf
sumber
Wow, jawaban yang bagus! +1
The Guy with The Hat
1) Keempat ststidak boleh dihitung sebagai panggilan overhead: ini adalah biaya untuk menyimpan hasil dalam variabel volatil, yang biasanya tidak Anda lakukan. 2) Di sistem saya (Arduino 1.0.5, gcc 4.8.2), saya tidak punya movw. Maka biaya panggilan millis()adalah: 4 siklus overhead panggilan + 15 siklus millis()itu sendiri = 19 siklus total (≈ 1,188 μs @ 16 MHz).
Edgar Bonet
1
@ EdgarBonet - Itu tidak masuk akal, xadalah uint16_t. Seharusnya paling banyak 2 salinan jika itu penyebabnya. Bagaimanapun, pertanyaannya adalah berapa lama millis()waktu yang digunakan , bukan ketika dipanggil saat mengabaikan hasilnya. Karena setiap penggunaan praktis akan melibatkan melakukan sesuatu dengan hasilnya, saya memaksakan hasilnya disimpan melalui volatile. Biasanya, efek yang sama akan dicapai dengan penggunaan variabel di kemudian hari yang diatur ke nilai balik panggilan, tetapi saya tidak ingin panggilan ekstra itu mengambil ruang dalam jawabannya.
Connor Wolf
Ini uint16_tdalam sumber tidak sesuai perakitan (4 byte disimpan ke dalam RAM). Anda mungkin memposting sumber dan membongkar dua versi berbeda.
Edgar Bonet
@ConnorWolf Jawaban dan penjelasan yang luar biasa. Terima kasih!
Lefteris
8

Tulis sketsa yang milis 1000 kali, bukan dengan membuat lingkaran, tetapi dengan menyalin dan menempel. Ukur itu dan bandingkan dengan waktu yang diharapkan sebenarnya. Pikiran Anda bahwa hasil dapat bervariasi dengan berbagai versi IDE (dan kompilernya pada khususnya).

Pilihan lain adalah untuk beralih pin IO sebelum dan sesudah panggilan millis, kemudian mengukur waktu untuk nilai yang sangat kecil dan nilai yang agak lebih besar. Bandingkan timing yang diukur dan hitung overhead.

Cara paling akurat adalah dengan melihat daftar pembongkaran, kode yang dihasilkan. Tetapi itu bukan untuk orang yang lemah hati. Anda harus mempelajari lembar data dengan cermat berapa lama setiap siklus instruksi berlangsung.

jippie
sumber
Bagaimana Anda mengukur waktu yang diambil oleh 1000 millis()panggilan?
apnorton
Anda tahu millis () disediakan oleh interupsi pada timer0 yang menambah variabel internal setiap centang?
TheDoctor
@ TheDoctor yang saya campur aduk delay, Anda benar. Tapi idenya tetap sama, Anda dapat mengatur waktu sejumlah besar panggilan dan rata-rata. Mematikan interupsi secara global mungkin bukan ide yang sangat bagus; o)
jippie
Pastikan kumpulan data Anda cukup besar karena pencetakan karakter ke Serial memerlukan beberapa milidetik. Saya tidak ingat waktu yang tepat, tapi saya pikir ini kira-kira ~ 0,6 ms per karakter yang dikirim ke Serial.
Steven10172
@ Steven10172 Anda dapat mengatur waktu string kosong terhadap string 1000 kali (atau lebih), maka Anda tahu delta dan pengukurannya lebih akurat.
jippie
3

Saya kedua memanggil millis berulang kali dan kemudian membandingkan aktual vs yang diharapkan.

Akan ada beberapa overhead minimal, tetapi akan berkurang secara signifikan semakin sering Anda menelepon millis ().

Jika Anda melihat

C:\Program Files (x86)\Arduino\Arduino ERW 1.0.5\hardware\arduino\cores\arduino\wiring.c

Anda dapat melihat bahwa millis () sangat kecil dengan hanya 4 instruksi (cli is simply # define cli() \__asm__ \__volatile__ ("cli" ::))dan pengembalian.

Saya akan menyebutnya sekitar 10 juta kali menggunakan loop FOR yang memiliki volatile sebagai kondisional. Kata kunci yang mudah menguap akan mencegah kompiler dari mencoba optimasi apa pun pada loop itu sendiri.

Saya tidak menjamin yang berikut ini sempurna secara sintaksis ..

int temp1,temp2;
temp1=millis();
for (volatile unsigned int j=0;j<1000000;++j){
temp2=millis();}
Serial.print("Execution time = ");
Serial.print((temp2-temp1,DEC);
Serial.print("ms");

Dugaan saya adalah yang membutuhkan ~ 900ms atau sekitar 56us per panggilan ke millis. (Saya tidak punya ATM berguna aruduino.

80HD
sumber
1
Anda harus mengubah int temp1,temp2;ke volatile int temp1,temp2;untuk mencegah kompiler dari berpotensi mengoptimalkan mereka.
Connor Wolf
Panggilan bagus untuk volatile. Saya benar-benar bermaksud memasukkannya ke dalam dan kemudian tidak melakukannya. Saya seharusnya juga menyebutkan bahwa cara untuk melakukan patokan yang lebih tepat adalah dengan menjalankan loop kosong, mencatat waktu eksekusi itu, kemudian menjalankan loop lagi saat melakukan pekerjaan. Kurangi perbedaannya, bagi dengan jumlah iterasi, dan ada waktu eksekusi Anda yang sangat akurat.
80HD
Benchmark semacam itu hanya berfungsi pada sistem yang tidak pernah melakukan eksekusi kode lebih dulu. Lingkungan arduino secara default memiliki gangguan berkala yang akan dieksekusi secara berkala. Solusi yang lebih baik adalah dengan mengganti pin pada setiap eksekusi, dan menggunakan timer beresolusi tinggi untuk mengukur laju toggle ketika menjalankan dan tidak menjalankan kode yang dipermasalahkan, luangkan waktu eksekusi minimum atas sejumlah sampel untuk masing-masing , kurangi baseline, dan perlakukan itu sebagai waktu eksekusi Anda. Dengan asumsi waktu eksekusi Anda lebih pendek daripada waktu minimum antara interupsi.
Connor Wolf