Làm thế nào tôi có thể xử lý cuộn qua millis ()?


73

Tôi cần đọc một cảm biến cứ sau năm phút, nhưng vì bản phác thảo của tôi cũng có những nhiệm vụ khác phải làm, tôi không thể chỉ delay()giữa các bài đọc. Có Blink không chậm trễ hướng dẫn gợi ý tôi viết mã dọc theo các dòng này:

void loop()
{
    unsigned long currentMillis = millis();

    // Read the sensor when needed.
    if (currentMillis - previousMillis >= interval) {
        previousMillis = currentMillis;
        readSensor();
    }

    // Do other stuff...
}

Vấn đề là nó millis()sẽ quay trở về 0 sau khoảng 49,7 ngày. Vì bản phác thảo của tôi dự định chạy lâu hơn thế, tôi cần chắc chắn rằng cuộn qua không làm cho bản phác thảo của tôi thất bại. Tôi có thể dễ dàng phát hiện điều kiện cuộn qua ( currentMillis < previousMillis), nhưng tôi không chắc phải làm gì sau đó.

Vì vậy, câu hỏi của tôi: điều gì sẽ là cách thích hợp / đơn giản nhất để xử lý millis()cuộn qua?


5
Biên tập lưu ý: Đây không chính xác là một câu hỏi của tôi, mà là một hướng dẫn trong một định dạng câu hỏi / câu trả lời. Tôi đã chứng kiến ​​rất nhiều sự nhầm lẫn trên Internet (bao gồm cả ở đây) về chủ đề này, và trang web này có vẻ như là nơi rõ ràng để tìm kiếm một câu trả lời. Đây là lý do tại sao tôi cung cấp hướng dẫn này ở đây.
Edgar Bonet

2
Tôi sẽ làm previousMillis += intervalthay vì previousMillis = currentMillisnếu tôi muốn một tần suất kết quả nhất định.
Jasen 7/2/2016

4
@Jasen: Đúng vậy! previousMillis += intervalnếu bạn muốn tần suất không đổi và chắc chắn rằng quá trình xử lý của bạn mất ít hơn interval, nhưng previousMillis = currentMillisđể đảm bảo độ trễ tối thiểu là interval.
Edgar Bonet

Chúng tôi thực sự cần một Câu hỏi thường gặp cho những thứ như thế này.

Một trong những "thủ thuật" tôi sử dụng là giảm tải cho arduino bằng cách sử dụng int nhỏ nhất chứa khoảng. Chẳng hạn, trong khoảng thời gian tối đa 1 phút, tôi viếtuint16_t previousMillis; const uint16_t interval = 45000; ... uint16_t currentMillis = (uint16_t) millis(); if ((currentMillis - previousMillis) >= interval) ...
frarugi87

Câu trả lời:


95

Câu trả lời ngắn gọn: đừng cố gắng để xử lý rollover Millis, thay vào đó hãy viết mã rollover an toàn. Mã ví dụ của bạn từ hướng dẫn là tốt. Nếu bạn cố gắng phát hiện cuộn qua để thực hiện các biện pháp khắc phục, rất có thể bạn đang làm sai điều gì đó. Hầu hết các chương trình Arduino chỉ phải quản lý các sự kiện kéo dài thời lượng tương đối ngắn, như gỡ nút trong 50 ms hoặc bật máy sưởi trong 12 giờ ... Sau đó, và ngay cả khi chương trình có nghĩa là chạy trong nhiều năm, cuộn dây millis không nên là một mối quan tâm.

Cách chính xác để quản lý (hay đúng hơn là tránh phải quản lý) vấn đề tái đầu tư là nghĩ về con unsigned longsố được trả về theo millis()thuật ngữ mô đun . Đối với những người có khuynh hướng toán học, một số quen thuộc với khái niệm này rất hữu ích khi lập trình. Bạn có thể thấy toán học đang hoạt động trong bài viết của Mill Gammon () tràn ngập ... một điều tồi tệ? . Đối với những người không muốn xem qua các chi tiết tính toán, tôi cung cấp ở đây một cách nghĩ khác (hy vọng đơn giản hơn) về nó. Nó được dựa trên sự phân biệt đơn giản giữa khoảnh khắckhoảng thời gian . Miễn là các xét nghiệm của bạn chỉ liên quan đến việc so sánh thời lượng, bạn sẽ ổn thôi.

Lưu ý trên micros () : Mọi thứ được nói ở đây millis()đều áp dụng như nhau micros(), ngoại trừ thực tế là micros()cứ sau 71,6 phút, và setMillis()chức năng được cung cấp dưới đây không ảnh hưởng micros().

Thời gian, dấu thời gian và thời lượng

Khi giao dịch với thời gian, chúng ta phải làm cho sự phân biệt giữa ít nhất hai khái niệm khác nhau: khoảnh khắckhoảng thời gian . Ngay lập tức là một điểm trên trục thời gian. Thời lượng là độ dài của một khoảng thời gian, tức là khoảng cách thời gian giữa các thời điểm xác định thời điểm bắt đầu và kết thúc của khoảng thời gian. Sự khác biệt giữa các khái niệm này không phải lúc nào cũng rất sắc nét trong ngôn ngữ hàng ngày. Ví dụ, nếu tôi nói “ Tôi sẽ trở lại trong năm phút ”, sau đó “ lăm phút ” là ước tính thời gian vắng mặt của tôi, trong khi đó “ trong năm phút ” là ngay lập tức dự đoán của tôi sẽ trở lại. Giữ sự khác biệt trong tâm trí là rất quan trọng, bởi vì đó là cách đơn giản nhất để hoàn toàn tránh được vấn đề tái đầu tư.

Giá trị trả về của millis()có thể được hiểu là thời lượng: thời gian trôi qua từ khi bắt đầu chương trình cho đến bây giờ. Sự giải thích này, tuy nhiên, bị phá vỡ ngay khi millis tràn ra. Nhìn chung sẽ hữu ích hơn rất nhiều khi nghĩ đến việc millis()trả về dấu thời gian , tức là nhãn của nhãn xác nhận xác định tức thời. Có thể lập luận rằng cách giải thích này bị các nhãn này không rõ ràng, vì chúng được sử dụng lại sau mỗi 49,7 ngày. Tuy nhiên, điều này hiếm khi là một vấn đề: trong hầu hết các ứng dụng nhúng, bất cứ điều gì xảy ra 49,7 ngày trước là lịch sử cổ đại mà chúng ta không quan tâm. Vì vậy, tái chế các nhãn cũ không phải là một vấn đề.

Không so sánh dấu thời gian

Cố gắng tìm ra cái nào trong số hai dấu thời gian lớn hơn cái kia không có ý nghĩa gì. Thí dụ:

unsigned long t1 = millis();
delay(3000);
unsigned long t2 = millis();
if (t2 > t1) { ... }

Ngây thơ, người ta sẽ mong muốn điều kiện if ()luôn luôn đúng. Nhưng nó thực sự sẽ là sai nếu millis tràn trong delay(3000). Nghĩ rằng t1 và t2 là nhãn có thể tái chế là cách đơn giản nhất để tránh lỗi: nhãn t1 rõ ràng đã được gán cho tức thì trước t2, nhưng trong 49,7 ngày, nó sẽ được gán lại cho tức thì trong tương lai. Do đó, t1 xảy ra cả trướcsau t2. Điều này sẽ làm rõ rằng biểu hiện t2 > t1không có ý nghĩa.

Nhưng, nếu đây chỉ là những nhãn hiệu, câu hỏi rõ ràng là: làm thế nào chúng ta có thể thực hiện bất kỳ phép tính thời gian hữu ích nào với chúng? Câu trả lời là: bằng cách giới hạn bản thân trong hai phép tính duy nhất có ý nghĩa đối với dấu thời gian:

  1. later_timestamp - earlier_timestampmang lại một khoảng thời gian, cụ thể là lượng thời gian trôi qua giữa tức thời trước đó và tức thời sau đó. Đây là hoạt động số học hữu ích nhất liên quan đến dấu thời gian.
  2. timestamp ± durationmang lại dấu thời gian sau một thời gian (nếu sử dụng +) hoặc trước (nếu -) dấu thời gian ban đầu. Không hữu ích như âm thanh, vì dấu thời gian kết quả có thể được sử dụng chỉ trong hai loại tính toán ...

Nhờ các tính năng mô-đun, cả hai đều được đảm bảo hoạt động tốt trên rollover millis, ít nhất là miễn là sự chậm trễ liên quan là ngắn hơn 49,7 ngày.

So sánh thời lượng là tốt

Thời lượng chỉ là lượng mili giây trôi qua trong một khoảng thời gian. Miễn là chúng ta không cần phải xử lý thời lượng dài hơn 49,7 ngày, bất kỳ hoạt động nào có ý nghĩa về mặt vật lý cũng sẽ có ý nghĩa về mặt tính toán. Ví dụ, chúng ta có thể nhân một khoảng thời gian với tần suất để có được một số khoảng thời gian. Hoặc chúng ta có thể so sánh hai thời lượng để biết cái nào dài hơn. Ví dụ, đây là hai triển khai thay thế của delay(). Đầu tiên, lỗi một:

void myDelay(unsigned long ms) {          // ms: duration
    unsigned long start = millis();       // start: timestamp
    unsigned long finished = start + ms;  // finished: timestamp
    for (;;) {
        unsigned long now = millis();     // now: timestamp
        if (now >= finished)              // comparing timestamps: BUG!
            return;
    }
}

Và đây là một trong những chính xác:

void myDelay(unsigned long ms) {              // ms: duration
    unsigned long start = millis();           // start: timestamp
    for (;;) {
        unsigned long now = millis();         // now: timestamp
        unsigned long elapsed = now - start;  // elapsed: duration
        if (elapsed >= ms)                    // comparing durations: OK
            return;
    }
}

Hầu hết các lập trình viên C sẽ viết các vòng lặp ở trên dưới dạng terser, như

while (millis() < start + ms) ;  // BUGGY version

while (millis() - start < ms) ;  // CORRECT version

Mặc dù trông chúng có vẻ giống nhau, nhưng việc phân biệt dấu thời gian / thời lượng sẽ làm rõ cái nào là lỗi và cái nào đúng.

Nếu tôi thực sự cần so sánh dấu thời gian thì sao?

Tốt hơn nên cố gắng tránh tình huống. Nếu điều đó là không thể tránh khỏi, vẫn còn hy vọng nếu biết rằng các chất tương ứng đã đủ gần: gần hơn 24,85 ngày. Có, độ trễ quản lý tối đa 49,7 ngày của chúng tôi đã bị cắt giảm một nửa.

Giải pháp rõ ràng là chuyển đổi vấn đề so sánh dấu thời gian của chúng tôi thành vấn đề so sánh thời lượng. Nói rằng chúng ta cần biết t1 tức thì là trước hay sau t2. Chúng tôi chọn một số tham chiếu tức thì trong quá khứ chung của chúng và so sánh thời lượng từ tham chiếu này cho đến cả t1 và t2. Thời gian tham chiếu có được bằng cách trừ một khoảng thời gian đủ dài từ t1 hoặc t2:

unsigned long reference_instant = t2 - LONG_ENOUGH_DURATION;
unsigned long from_reference_until_t1 = t1 - reference_instant;
unsigned long from_reference_until_t2 = t2 - reference_instant;
if (from_reference_until_t1 < from_reference_until_t2)
    // t1 is before t2

Điều này có thể được đơn giản hóa như:

if (t1 - t2 + LONG_ENOUGH_DURATION < LONG_ENOUGH_DURATION)
    // t1 is before t2

Nó là cám dỗ để đơn giản hóa hơn nữa vào if (t1 - t2 < 0). Rõ ràng, điều này không hoạt động, bởi vì t1 - t2, được tính là một số không dấu, không thể âm. Điều này, tuy nhiên, mặc dù không di động, nhưng hoạt động:

if ((signed long)(t1 - t2) < 0)  // works with gcc
    // t1 is before t2

Từ khóa signedở trên là dư thừa (một đồng bằng longluôn được ký), nhưng nó giúp làm rõ ý định. Chuyển đổi thành một chữ ký dài tương đương với cài đặt LONG_ENOUGH_DURATIONbằng 24,85 ngày. Thủ thuật này không khả dụng vì theo tiêu chuẩn C, kết quả là việc thực hiện được xác định . Nhưng vì trình biên dịch gcc hứa hẹn sẽ làm điều đúng đắn , nó hoạt động đáng tin cậy trên Arduino. Nếu chúng tôi muốn tránh thực hiện hành vi được xác định, so sánh đã ký ở trên tương đương về mặt toán học với điều này:

#include <limits.h>

if (t1 - t2 > LONG_MAX)  // too big to be believed
    // t1 is before t2

với vấn đề duy nhất là sự so sánh có vẻ ngược. Nó cũng tương đương, miễn là 32 bit, đối với thử nghiệm một bit này:

if ((t1 - t2) & 0x80000000)  // test the "sign" bit
    // t1 is before t2

Ba thử nghiệm cuối cùng thực sự được gcc biên dịch thành cùng một mã máy.

Làm cách nào để kiểm tra bản phác thảo của tôi chống lại cuộn dây millis

Nếu bạn tuân theo các giới luật ở trên, bạn sẽ được tốt. Nếu bạn vẫn muốn kiểm tra, hãy thêm chức năng này vào bản phác thảo của bạn:

#include <util/atomic.h>

void setMillis(unsigned long ms)
{
    extern unsigned long timer0_millis;
    ATOMIC_BLOCK (ATOMIC_RESTORESTATE) {
        timer0_millis = ms;
    }
}

và bây giờ bạn có thể du hành thời gian chương trình của bạn bằng cách gọi setMillis(destination). Nếu bạn muốn nó đi qua hàng triệu lần tràn qua nhiều lần, như Phil Connors hồi tưởng lại Ngày con rắn, bạn có thể đặt cái này vào trong loop():

// 6-second time loop starting at rollover - 3 seconds
if (millis() - (-3000) >= 6000)
    setMillis(-3000);

Dấu thời gian âm ở trên (-3000) được trình biên dịch ngầm chuyển thành một dấu dài không dấu tương ứng với 3000 mili giây trước khi cuộn qua (nó được chuyển đổi thành 4294964296).

Điều gì xảy ra nếu tôi thực sự cần theo dõi thời lượng rất dài?

Nếu bạn cần bật một rơle và tắt nó ba tháng sau đó, thì bạn thực sự cần phải theo dõi các tràn millis. Có nhiều cách để làm như vậy. Giải pháp đơn giản nhất có thể chỉ đơn giản là mở rộng millis() đến 64 bit:

uint64_t millis64() {
    static uint32_t low32, high32;
    uint32_t new_low32 = millis();
    if (new_low32 < low32) high32++;
    low32 = new_low32;
    return (uint64_t) high32 << 32 | low32;
}

Điều này về cơ bản là đếm các sự kiện tái đầu tư và sử dụng số này là 32 bit quan trọng nhất của số đếm mili giây 64 bit. Để tính toán này hoạt động bình thường, hàm cần được gọi ít nhất một lần sau mỗi 49,7 ngày. Tuy nhiên, nếu nó chỉ được gọi một lần trong 49,7 ngày, đối với một số trường hợp, có thể kiểm tra (new_low32 < low32)không thành công và mã bị mất một số lượng high32. Sử dụng millis () để quyết định khi nào thực hiện cuộc gọi duy nhất tới mã này trong một "gói" millis (cửa sổ 49,7 ngày cụ thể) có thể rất nguy hiểm, tùy thuộc vào cách sắp xếp khung thời gian. Để an toàn, nếu sử dụng millis () để xác định thời điểm thực hiện các cuộc gọi duy nhất đến millis64 (), nên có ít nhất hai cuộc gọi trong mỗi cửa sổ 49,7 ngày.

Mặc dù vậy, hãy ghi nhớ rằng số học 64 bit đắt tiền trên Arduino. Có thể đáng để giảm độ phân giải thời gian để giữ ở mức 32 bit.


2
Vì vậy, bạn đang nói rằng mã được viết trong câu hỏi sẽ thực sự hoạt động chính xác?
Jasen 7/2/2016

3
@Jasen: Chính xác! Tôi dường như đã hơn một lần mọi người cố gắng khắc phục lỗi khắc phục sự cố không tồn tại ở nơi đầu tiên.
Edgar Bonet

2
Tôi rất vui vì tôi đã tìm thấy điều này. Tôi đã có câu hỏi này trước đây.
Sebastian Freeman

1
Một trong những câu trả lời hay nhất và hữu ích nhất trên StackExchange! Cảm ơn rất nhiều! :)
Falko

Đây là một câu trả lời tuyệt vời cho câu hỏi. Tôi trở lại câu trả lời này về cơ bản mỗi năm một lần vì tôi bị hoang tưởng về những cuộc đua xe lộn xộn.
Jeffrey Cash

17

TL; DR Phiên bản ngắn:

An unsigned longlà 0 đến 4.294.967.295 (2 ^ 32 - 1).

Vì vậy, giả sử previousMillislà 4.294.967.290 (5 ms trước khi cuộn qua) và currentMillislà 10 (10ms sau khi cuộn qua). Sau đó currentMillis - previousMillislà thực tế 16 (không phải -4,294,967,280) vì kết quả sẽ được tính là một dấu dài (không thể âm, do đó, nó sẽ tự xoay quanh). Bạn có thể kiểm tra điều này đơn giản bằng cách:

Serial.println( ( unsigned long ) ( 10 - 4294967290 ) ); // 16

Vì vậy, mã trên sẽ hoạt động hoàn toàn tốt. Bí quyết là luôn tính chênh lệch thời gian và không so sánh hai giá trị thời gian.


Làm thế nào về 15ms trước khi cuộn qua và 10ms sau khi cuộn lại (tức là 49,7 ngày sau ). 15> 10 , nhưng tem 15ms đã gần một tháng rưỡi. Logic 15-10> 0 và 10-15> 0 unsigned , vì vậy không được sử dụng ở đây!
ps95

@ prakharsingh95 10ms-15ms sẽ trở thành ~ 49,7 ngày - 5ms, đây là sự khác biệt chính xác. Toán học hoạt động cho đến khi millis()cuộn qua hai lần, nhưng điều đó rất khó xảy ra đối với mã được đề cập.
BrettAM

Hãy để tôi nói lại. Giả sử bạn có hai dấu thời gian 200ms và 10ms. Làm thế nào để bạn biết đó là (đang) lăn qua?
ps95

@ prakharsingh95 Cái được lưu trữ previousMillisphải được đo trước đó currentMillis, vì vậy nếu currentMillisnhỏ hơn previousMilliscuộn qua xảy ra. Toán học xảy ra để giải quyết rằng trừ khi hai rollover đã xảy ra, bạn thậm chí không cần phải suy nghĩ về nó.
BrettAM

1
À, được rồi nếu bạn làm như vậy t2-t1, và nếu bạn có thể đảm bảo t1được đo trước t2đó thì nó tương đương với đã ký (t2-t1)% 4,294,967,295 , do đó, tự động hoàn thành. Đẹp!. Nhưng nếu có hai rollover, hoặc intervallà> 4.294.967.295 thì sao?
ps95

1

Bọc millis()trong một lớp học!

Logic VCL:

  1. Sử dụng id thay vì millis()trực tiếp.
  2. So sánh sự đảo ngược bằng cách sử dụng id. Điều này là sạch sẽ và tái đầu tư độc lập.
  3. Đối với các ứng dụng cụ thể, để tính toán sự khác biệt chính xác giữa hai id, hãy theo dõi các đảo ngược và tem. Tính hiệu số.

Theo dõi các đảo ngược:

  1. Cập nhật một tem địa phương định kỳ nhanh hơn millis(). Điều này sẽ giúp bạn tìm ra nếu millis()đã tràn.
  2. Thời gian của bộ định thời xác định độ chính xác
class Timer {

public:
    static long last_stamp;
    static long *stamps;
    static int *reversals;
    static int count;
    static int reversal_count;

    static void setup_timer() {
        // Setup Timer2 overflow to fire every 8ms (125Hz)
        //   period [sec] = (1 / f_clock [sec]) * prescale * (255-count)
        //                  (1/16000000)  * 1024 * (255-130) = .008 sec


        TCCR2B = 0x00;        // Disable Timer2 while we set it up

        TCNT2  = 130;         // Reset Timer Count  (255-130) = execute ev 125-th T/C clock
        TIFR2  = 0x00;        // Timer2 INT Flag Reg: Clear Timer Overflow Flag
        TIMSK2 = 0x01;        // Timer2 INT Reg: Timer2 Overflow Interrupt Enable
        TCCR2A = 0x00;        // Timer2 Control Reg A: Wave Gen Mode normal
        TCCR2B = 0x07;        // Timer2 Control Reg B: Timer Prescaler set to 1024

        count = 0;
        stamps = new long[50];
        reversals = new int [10];
        reversal_count =0;
    }

    static long get_stamp () {
        stamps[count++] = millis();
        return count-1;
    }

    static bool compare_stamps_by_id(int s1, int s2) {
        return s1 > s2;
    }

    static long long get_stamp_difference(int s1, int s2) {
        int no_of_reversals = 0;
        for(int j=0; j < reversal_count; j++)
        if(reversals[j] < s2 && reversals[j] > s1)
            no_of_reversals++;
        return stamps[s2]-stamps[s1] + 49.7 * 86400 * 1000;       
    }

};

long Timer::last_stamp;
long *Timer::stamps;
int *Timer::reversals;
int Timer::count;
int Timer::reversal_count;

ISR(TIMER2_OVF_vect) {

    long stamp = millis();
    if(stamp < Timer::last_stamp) // reversal
        Timer::reversals[Timer::reversal_count++] = Timer::count;
    else 
        ; // no reversal
    Timer::last_stamp = stamp;    
    TCNT2 = 130;     // reset timer ct to 130 out of 255
    TIFR2 = 0x00;    // timer2 int flag reg: clear timer overflow flag
};

// Usage

void setup () {
    Timer::setup_timer();

    long s1 = Timer::get_stamp();
    delay(3000);
    long s2 = Timer::get_stamp();

    Timer::compare_stamps_by_id(s1, s2); // true

    Timer::get_stamp_difference(s1, s2); // return true difference, taking into account reversals
}

Tín dụng hẹn giờ .


9
Tôi đã chỉnh sửa mã để loại bỏ các lỗi maaaaany đã ngăn nó biên dịch. Công cụ này sẽ tiêu tốn của bạn khoảng 232 byte RAM và hai kênh PWM. Nó cũng sẽ bắt đầu hỏng bộ nhớ sau bạn get_stamp()51 lần. So sánh độ trễ thay vì dấu thời gian chắc chắn sẽ hiệu quả hơn.
Edgar Bonet

1

Tôi thích câu hỏi này, và những câu trả lời tuyệt vời mà nó tạo ra. Đầu tiên là nhận xét nhanh về câu trả lời trước đó (tôi biết, tôi biết, nhưng tôi chưa có đại diện để nhận xét. :-).

Câu trả lời của Edgar Bonet thật tuyệt vời. Tôi đã viết mã được 35 năm và tôi đã học được một điều mới ngày hôm nay. Cảm ơn bạn. Điều đó nói rằng, tôi tin rằng mã cho "Điều gì sẽ xảy ra nếu tôi thực sự cần theo dõi thời lượng rất dài?" nghỉ trừ khi bạn gọi millis64 () ít nhất một lần cho mỗi lần tái đầu tư. Thực sự khó chịu, và không chắc là một vấn đề trong việc thực hiện trong thế giới thực, nhưng bạn đã đến đó.

Bây giờ, nếu bạn thực sự muốn dấu thời gian bao trùm bất kỳ phạm vi thời gian lành mạnh nào (64 bit của mili giây là khoảng nửa tỷ năm theo tính toán của tôi), có vẻ đơn giản để mở rộng triển khai millis () hiện tại lên 64 bit.

Những thay đổi này đối với attinycore / Wired.c (Tôi đang làm việc với ATTiny85) dường như hoạt động (Tôi giả sử mã cho các AVR khác là rất giống nhau). Xem các dòng có // bình luận BFB và hàm millis64 () mới. Rõ ràng nó sẽ lớn hơn (98 byte mã, 4 byte dữ liệu) và chậm hơn, và như Edgar đã chỉ ra, bạn gần như chắc chắn có thể hoàn thành mục tiêu của mình chỉ bằng cách hiểu rõ hơn về toán học số nguyên không dấu, nhưng đó là một bài tập thú vị .

volatile unsigned long long timer0_millis = 0;      // BFB: need 64-bit resolution

#if defined(__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
ISR(TIM0_OVF_vect)
#else
ISR(TIMER0_OVF_vect)
#endif
{
    // copy these to local variables so they can be stored in registers
    // (volatile variables must be read from memory on every access)
    unsigned long long m = timer0_millis;       // BFB: need 64-bit resolution
    unsigned char f = timer0_fract;

    m += MILLIS_INC;
    f += FRACT_INC;
    if (f >= FRACT_MAX) {
        f -= FRACT_MAX;
        m += 1;
    }

    timer0_fract = f;
    timer0_millis = m;
    timer0_overflow_count++;
}

// BFB: 64-bit version
unsigned long long millis64()
{
    unsigned long long m;
    uint8_t oldSREG = SREG;

    // 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();
    m = timer0_millis;
    SREG = oldSREG;

    return m;
}

1
Bạn nói đúng, tôi millis64()chỉ làm việc nếu nó được gọi thường xuyên hơn thời gian tái đầu tư. Tôi chỉnh sửa câu trả lời của tôi để chỉ ra giới hạn này. Phiên bản của bạn không có vấn đề này, nhưng nó có một nhược điểm khác: đó là các bản sao 64 bit trong ngữ cảnh bị gián đoạn , đôi khi làm tăng độ trễ trong việc đáp ứng các ngắt khác.
Edgar Bonet
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.