Điều chỉnh tính toán thời gian sau khi thay đổi tần số Timer0


7

Tôi có một Arduino Nano với 328P và cần tất cả 6 chân PWM.

Vì vậy, tôi đã phải điều chỉnh bộ đếm gộp trước và Chế độ WGM của Timer0.

Bây giờ nó đang ở chế độ PWM đúng pha với tỷ lệ trước là 1.

TCCR0A = _BV(COM0A1) | _BV(COM0B1) | _BV(WGM00);
TCCR0B = _BV(CS00);

Bây giờ tôi cần một phép tính thời gian làm việc cho các thư viện khác, nhưng vì Timer0 có nhiệm vụ đó nên mọi thứ đã không còn hoạt động nữa.

Tôi đã thử điều chỉnh hệ thống dây điện.

// the prescaler is set so that timer0 ticks every 64 clock cycles, and the
// the overflow handler is called every 256 ticks.
#define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(64 * 256))

đến đây

#define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(1 * 510))

Nhưng nó giống như tôi đã không thay đổi bất cứ điều gì. (đã thử nghiệm các cài đặt khác đã được thay đổi để nó được biên dịch lại)

Toàn bộ mã:

void setup() {

  // Set Timer 0, 1 and 2
  // Register A: Output A and B to non-inverted PWM and PWM mode to phase correct.
  // Register B: Pre Scaler to 1.
  TCCR0A = _BV(COM0A1) | _BV(COM0B1) | _BV(WGM00);
  TCCR0B = _BV(CS00);

  TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(WGM10);
  TCCR1B = _BV(CS10);

  TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM20);
  TCCR2B = _BV(CS20);

  pinMode(8, OUTPUT);


}
void loop() {

  digitalWrite(8, LOW);
  delay(65000);
  digitalWrite(8, HIGH);
  delay(65000);

}

Tất cả các cài đặt của bạn là chính xác. Tôi đoán tốt nhất nếu bạn có thể sửa đổi hệ thống dây sai. Cách tốt nhất để chẩn đoán điều này là đi đến Tệp> Tùy chọn và kiểm tra hộp kiểm biên dịch trong "Hiển thị đầu ra dài dòng trong khi". Sau đó bấm xác minh, sao chép đầu ra vào một trình soạn thảo văn bản và tìm kiếm hệ thống dây điện và xem nó được kéo từ đâu, sau đó mở tệp và xác minh rằng các thay đổi của bạn đang ở đó.
Jake C

Khi tôi dọn dẹp thư mục tmp và xác minh một lần nữa, nó hiển thị: Cái này. Và đó cũng là hệ thống dây điện. Tôi đã sửa :(
Splitframe

Tôi không biết phải nói gì, bằng mọi cách nên làm việc. Một lựa chọn khác là chỉ tự mình mở rộng quy mô . Bạn có thể gói cái này trong macro a la #define SCALE_UP(x) x<<6và sau đó sử dụng nó như thếdelay(SCALE_UP(1000))
Jake C

Đó là điều kỳ lạ, giá trị thực sự dường như thực sự độc đoán. Tôi đã thử hai nanos và một Uno, cả hai đều có kết quả như nhau. Và khi tôi cố gắng tính giá trị cho độ trễ, tôi nhận được 64000, nhưng khi tôi chèn nó chỉ như 800ms thay vì 1000ms. Đối với mục đích thử nghiệm, tôi đã loại bỏ tất cả # bao gồm chỉ là thay đổi Đăng ký và DigitalWrite và trì hoãn ngay bây giờ.
Splitframe

Bạn đo như thế nào? Bạn có một máy hiện sóng tình cờ?
Jake C

Câu trả lời:


5

Sửa các chức năng chấm công bằng các cài đặt PWM của bạn không đơn giản như vậy. Bạn ít nhất nên cố gắng viết lại ISR(TIMER0_OVF_vect), micros()và có lẽ delay(). Đây là lý do tại sao:

Đầu tiên, có một vấn đề làm tròn. Thời gian được giữ bằng hai biến toàn cục:

volatile unsigned long timer0_millis;
static unsigned char timer0_fract;

Đầu tiên là những gì millis()trở lại. Cái thứ hai theo dõi bao nhiêu thời gian đã trôi qua kể từ mili giây đầy đủ cuối cùng, và nó làm như vậy theo đơn vị 8 con. Hai biến được tăng lên ISR(TIMER0_OVF_vect)như thế này:

m += MILLIS_INC;  // temporary copy of timer0_millis
f += FRACT_INC;   // temporary copy of timer0_fract

Trên cấu hình Uno bình thường, ISR được gọi là 1024 1024. Sau đó MILLIS_INClà 1 và FRACT_INClà 3. Với cấu hình hẹn giờ của bạn, ISR được gọi là mỗi 31.875 Bọ (510 chu kỳ), sau đó MILLIS_INCsẽ là 0 và FRACT_INCphải là 3.984375. Nhưng vì chúng tôi đang xử lý các số nguyên, nó sẽ được làm tròn xuống còn 3 và bạn millis()sẽ đánh dấu vào khoảng 25% quá chậm.

Một sửa chữa đơn giản sẽ là

#define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(1 * 512))

theo thứ tự cho FRACT_INClà 4 và millis()là 0,4% quá nhanh. Hoặc bạn có thể tạo biến timer0_fract16 bit và đếm số chu kỳ xung nhịp, chỉ để tránh lỗi này. Hoặc là tùy chọn nên khắc phục millis(), nhưng bạn vẫn có vấn đề với micros().

micros()hoạt động bằng cách đọc cả hai timer0_overflow_count(tăng 1 trong ISR) và giá trị bộ đếm thực tế. Vì bộ đếm của bạn hiện đang thay thế lên xuống, nên việc tính số micrô giây từ các bài đọc này sẽ khó hơn. Có lẽ bạn có thể mất hai lần đọc liên tiếp của quầy, chỉ để biết liệu nó đang tăng hay giảm ...

Và sau đó, có delay(), dựa vào micros(). Nếu bạn sửa chữa micros(), delay()nên làm việc tốt. Nếu không, bạn có thể viết lại delay()để sử dụng millis()thay thế, điều này sẽ dễ dàng nhưng bạn sẽ mất một số độ chính xác.


Cảm ơn câu trả lời tinh vi! Tại thời điểm này cũng cảm ơn @jakec đã thảo luận vấn đề với tôi!
Splitframe

Since your counter is now going alternatively up and downTại sao TCNT0đi lên hay xuống? Nó sẽ tăng lên và mỗi khi nó tràn qua ISR được gọi. Tui bỏ lỡ điều gì vậy? Tôi đã cố gắng giải quyết điều này bằng cách chỉ tăng timer0_overflow_count64 lần một lần và trên micros()hàm trả về thời gian vì return ((m << 8) + (t % 64)) * (64 / clockCyclesPerMicrosecond());vậy tôi thực hiện một mod trên bộ TCNT0đếm vì Timer của tôi đang tích tắc nhanh hơn 64 lần. Nhưng mặc dù micros()có vẻ ổn delay()nhưng vẫn chạy nhanh hơn và tôi không hiểu tại sao ...
Lefteris

@Lefteris: Bộ đếm đang lên xuống vì OP đã cấu hình nó ở chế độ PWM đúng pha.
Edgar Bonet

Sửa lỗi ở trên, modulo đã sai, tôi chia 64 cho hàm trả về bây giờ: return ((m << 8) + (t/64) ) * (64 / clockCyclesPerMicrosecond());Vẫn còn vấn đề với chức năng trì hoãn mà tôi không hiểu
Lefteris

@EdgarBonet Nếu tôi hiểu đúng, TCTN0đăng ký sẽ tăng mỗi lần đánh dấu CLK / 64. Vì vậy, nó chỉ đi lên. Nó được thiết lập lại mỗi khi nó tràn ra và TIMER0_OVF_vectISR được gọi. Vì vậy, vẫn không hiểu những gì đúng pha PWM phải làm với điều này.
Lefteris

0

Bạn đã đặt MICROSECONDS_PER_TIMER0_OVERFLOWthành một giá trị phù hợp, tuy nhiên, điều này chỉ được sử dụng bởi MILLIS_INC, mà đến lượt nó chỉ được sử dụng bởi millis(). Điều này có nghĩa rằng các chức năng thời gian khác, chẳng hạn như micros(), delay(), delayMicroseconds()phá vỡ khi timer0 được thay đổi. Đây là một loại lỗi và nó có thể được sửa trong phiên bản tương lai, nhưng hiện tại, các thư viện Arduino đang mong đợi bạn để lại timer0 một mình. Cách giải quyết tốt nhất là chỉ sử dụng millis()cho các chức năng quan trọng về thời gian của bạn.


0

ĐÂY LÀ MỘT TRẢ LỜI TUYỆT VỜI - Edgar biết anh ta đang nói về điều gì, hãy lắng nghe anh ta

Tôi hiện đang làm việc với điều tương tự nhưng trong ATMega2560. Trang web nàyđã giúp tôi hiểu hàm millis () tốt hơn. Bộ đếm thời gian tràn ra sau mỗi lần đếm 510 (xem bảng dữ liệu, trang 123 cho atmega2560). Tôi tin rằng đó là 510 chứ không phải 512 vì nó đếm lên và xuống, nhưng không lặp lại số đếm ở trên cùng hoặc dưới cùng - ví dụ: nếu bạn đếm bắt đầu từ 1 đến 10 rồi quay xuống (không lặp lại 10) bạn sẽ có đếm 19 lần, không phải 20. Bộ đếm này bắt đầu từ 0, đếm đến 255 và sau đó trở về 0, kích hoạt tràn trước khi nhấn 0 lần nữa. Đây là 256 (đếm lên bao gồm 0 và 255) + 254 (đếm ngược, không bao gồm 255 hoặc 0) = 510. Tôi đã viết một tập lệnh python để hình dung hiệu quả của những thay đổi này và cố gắng tính đến chúng. Con số này lên tới 10 triệu, vì vậy cần có thời gian để chạy, nhưng cuối cùng, ngay cả khi chia tỷ lệ, lỗi nếu chỉ điều chỉnh millis () sau khi thực tế có thể là ~ 7 giây.

#! /usr/bin/env python
import numpy as np

realT = np.zeros(10000000)
timer0_millis = np.zeros(10000000)
timer0_fract = np.zeros(10000000)
i=0

for i in range (1,10000000):
    timer0_millis[i] = timer0_millis[i-1] + 1
    timer0_fract[i] = timer0_fract[i-1] + 3
    realT[i] = i*31875
    if timer0_fract[i-1] >= 125:
        timer0_fract[i] = timer0_fract[i-1] - 125
        timer0_millis[i] = timer0_millis[i-1] + 1

adjusted = timer0_millis*510/(256*64)

print "after 100 timer interrupts millis() will read %d, actual millis is %.6f, adjusted millis is %.6f" % (timer0_millis[99], realT[99]/1000000, adjusted[99])
print "after 1000 timer interrupts millis() will read %d, actual millis is %.6f, adjusted millis is %.6f" % (timer0_millis[999], realT[999]/1000000, adjusted[999])
print "after 10000 timer interrupts millis() will read %d, actual millis is %.6f, adjusted millis is %.6f" % (timer0_millis[9999], realT[9999]/1000000, adjusted[9999])
print "after 100000 timer interrupts millis() will read %d, actual millis is %.6f, adjusted millis is %.6f" % (timer0_millis[99999], realT[99999]/1000000, adjusted[99999])
print "after 1000000 timer interrupts millis() will read %d, actual millis is %.6f, adjusted millis is %.6f" % (timer0_millis[999999], realT[999999]/1000000, adjusted[999999])
print "after 10000000 timer interrupts millis() will read %d, actual millis is %.6f, adjusted millis is %.6f" % (timer0_millis[9999999], realT[9999999]/1000000, adjusted[9999999])

Bạn đã viết: Từ chối Tôi tin vào chế độ đúng pha, bộ đếm thời gian tràn ra sau mỗi 510 số đếm, chứ không phải 512 như được chỉ định bởi Edgar . Xin vui lòng, đọc lại câu trả lời của tôi, và đừng hiểu lầm tôi.
Edgar Bonet

Xin lỗi, tôi đã đề cập đến dòng này "#define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds (1 * 512))" - Tôi đã cập nhật nhận xét của tôi, chính xác cho tôi nếu tôi sai ở đây, tôi không có ý định dẫn chứng sai
Nerbsie

Có một lý do rất tốt để có 512 thay vì 510 trong dòng này. Đọc câu trả lời của tôi và bạn sẽ hiểu.
Edgar Bonet

Được rồi, vì vậy bạn đang thêm hai số đếm thời gian, mỗi lần là 62,5ns, để tính đến lỗi gây ra bởi việc làm tròn bắt buộc?
Nerbsie

1
Edgar, nếu bạn muốn tiếp tục giúp tôi, tôi đã quyết định rằng thay vì trả lời một câu hỏi bằng những thông tin sai lệch của chính tôi và tiềm năng, tôi sẽ hỏi một câu hỏi
Nerbsie

0

Nhờ câu trả lời của Edgar và lời giải thích của Nebsie về cách bộ đếm hoạt động chính xác, tôi có thể tự mình thực hiện cách khắc phục độ trễ và micros () mà - cho đến nay - dường như hoạt động tốt trên Arduino Uno của tôi.

Tôi không nói rằng đây là cách triển khai chính xác nhất, đặc biệt là tôi nghi ngờ về micros () nếu ví dụ như một bộ đếm thời gian tràn qua giữa việc đọc t1 và t2, nhưng nó là một hoạt động tốt cho đến nay.

Điều đầu tiên - theo khuyến nghị của Edgar tôi đã xác định:

#define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(1 * 512)) 

Tuy nhiên, điều này không ảnh hưởng đến thời gian của micros () hoặc delay ().

Hàm micros () Tôi đã thay đổi để đọc hai lần từ TCNT0 - để xác định xem nó đang đếm lên hay xuống. Điều này được thực hiện với mệnh đề "if (t1> t2)". Số lượng tràn "m" được nhân với 510, vì bộ đếm trôi qua sau 510 bước. Sau đó, giá trị bộ đếm được tính "t" được thêm vào này, chia cho số lượng đồng hồ trên mỗi giây. (Lưu ý: Prescaler = 1, do đó không có nhiều ứng dụng nữa).

unsigned long micros() {
    unsigned long m;
    uint8_t oldSREG = SREG, t1, t2;
    uint16_t t;

    cli();
    m = timer0_overflow_count;
#if defined(TCNT0)
    t1 = TCNT0;
#elif defined(TCNT0L)
    t1 = TCNT0L;
#else
    #error TIMER 0 not defined
#endif

#if defined(TCNT0)
    t2 = TCNT0;
#elif defined(TCNT0L)
    t2 = TCNT0L;
#else
    #error TIMER 0 not defined
#endif

    if (t1 >= t2) {
        t = 510 - t2;       // counter running backwards
    } else {
        t = t2;             // counter running upwards
    }       

#ifdef TIFR0
    if ((TIFR0 & _BV(TOV0)) && (t2 > 1))        // if overflow flag is set -> increase m
        m++;
#else
    if ((TIFR & _BV(TOV0)) && (t2 > 1))
        m++;
#endif

    SREG = oldSREG;

    return ((m * 510) + t)  / clockCyclesPerMicrosecond();          
}

TUY NHIÊN - điều này có vẻ hoạt động tốt nhưng tôi đã có vấn đề để bắt đầu (trước khi chỉnh sửa lại). Do bộ đếm chạy nhanh hơn nhiều, cứ sau 268 giây, kiểu dữ liệu dài không dấu của micros () lại tràn ra và bắt đầu lại từ số không. Điều này dẫn đến việc khóa độ trễ không mong muốn (), đặc biệt là nếu thời gian trễ dài, như trong trường hợp của tôi, độ trễ của một giây trên mỗi vòng lặp được sử dụng. Do đó, tôi cũng đã phải thêm một phát hiện tràn vào hàm delay () trong arduino Wired.c.

Nếu tràn xảy ra, thời gian có thể không chính xác. Nhưng vì đây là một lần quá 268 giây nên nó có thể được chấp nhận. Cho đến nay với sự thay đổi này, chức năng trì hoãn hoạt động tốt trở lại về phía tôi.

void delay(unsigned long ms)
{
    uint32_t start = micros();
    uint32_t elapsed = 0;   

    while (ms > 0) {
        yield();
        while ( ms > 0 && (micros() - 1000) >= (start + elapsed)) {         
            ms--;
            elapsed += 1000;            
        }
        if( start > micros() ) {    // overflow detected
            start = micros();       // reset start
            elapsed = 0;            // reset elapsed
        }
    }
}
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.