Tôi có thể làm cho delayMicroseconds chính xác hơn không?


8

Tôi đang cố gắng đập dữ liệu DMX và yêu cầu 4us xung. Không gặp nhiều may mắn với kết quả mà tôi đang kiểm tra để xem Arduino chậm trễ đến mức nào ... Có vẻ như nó khá khủng khiếp với nó.

Đây là một thử nghiệm nhỏ tôi đã làm:

unsigned long ptime;

void setup() {
  Serial.begin(9600);
}

void loop() {
  ptime = micros();
  delayMicroseconds(4);
  Serial.println(micros() - ptime);

  delay(1000); //just to keep the serial monitor from going nuts
}

Và kết quả:

8 4 8 4 4 4 4 4 8 8 8 8 4 8 8 4 4 8 4 4 4 8 8 8 4 8 4 4

Tôi đã bị sốc khi biết độ chính xác của nó tệ đến mức nào. Đó là gấp đôi thời gian tôi muốn trì hoãn, nhưng nó thậm chí không nhất quán với nơi tôi chỉ có thể chia cho 2!

Có bất cứ điều gì tôi có thể làm để có được kết quả chính xác, phù hợp?


Tại sao bạn lại đập bit thay vì truy cập trực tiếp vào UART?
Ignacio Vazquez-Abrams

Vì vậy, tôi có thể có nhiều hơn một đầu ra.
bwoogie

Mega có bốn UART. Ngay cả khi bạn giữ một cái vĩnh viễn để lập trình mà vẫn cung cấp cho bạn ba vũ trụ.
Ignacio Vazquez-Abrams

Tôi chỉ đang thử nghiệm với mega vì đó là những gì tôi hiện có sẵn, dự án cuối cùng sẽ có ATMEGA328
bwoogie

1
Vấn đề ở đây là cách bạn đo lường.
Gerben

Câu trả lời:


7

Như đã giải thích trong các câu trả lời trước, vấn đề thực tế của bạn không phải là độ chính xác delayMicroseconds(), mà là độ phân giải của micros().

Tuy nhiên, để trả lời câu hỏi thực tế của bạn , có một cách thay thế chính xác hơn cho delayMicroseconds(): chức năng _delay_us()từ AVR-libc là chính xác theo chu kỳ và, ví dụ:

_delay_us(1.125);

Thực hiện chính xác những gì mà nó nói. Thông báo chính là đối số phải là hằng số thời gian biên dịch. Bạn phải #include <util/delay.h>có quyền truy cập vào chức năng này.

Cũng lưu ý rằng bạn phải chặn các ngắt nếu bạn muốn bất kỳ loại độ trễ chính xác nào.

Chỉnh sửa : Ví dụ: nếu tôi tạo xung 4 xung trên PD2 (chân 19 trên Mega), tôi sẽ tiến hành như sau. Đầu tiên, lưu ý rằng đoạn mã sau

noInterrupts();
PORTD |= _BV(PD2);   // PD2 HIGH
PORTD &= ~_BV(PD2);  // PD2 LOW
interrupts();

tạo ra xung dài 0,125 (2 chu kỳ CPU), vì đó là thời gian cần thiết để thực hiện lệnh đặt cổng LOW. Sau đó, chỉ cần thêm thời gian bị thiếu trong một độ trễ:

noInterrupts();
PORTD |= _BV(PD2);   // PD2 HIGH
_delay_us(3.875);
PORTD &= ~_BV(PD2);  // PD2 LOW
interrupts();

và bạn có độ rộng xung chính xác theo chu kỳ. Điều đáng chú ý là điều này không thể đạt được digitalWrite(), vì một cuộc gọi đến chức năng này mất khoảng 5 Lời nói.


3

Kết quả kiểm tra của bạn là sai lệch. delayMicroseconds()thực sự trì hoãn khá chính xác (đối với độ trễ hơn 2 hoặc 3 micro giây). Bạn có thể kiểm tra mã nguồn của nó trong tệp /usr/share/arduino/hardware/arduino/cores/arduino/wires.c (trên hệ thống Linux; hoặc trên một số đường dẫn tương tự trên các hệ thống khác).

Tuy nhiên, độ phân giải micros()là bốn micro giây. (Xem, ví dụ: trang garretlab vềmicros() .) Do đó, bạn sẽ không bao giờ thấy việc đọc trong khoảng từ 4 micro giây đến 8 micro giây. Độ trễ thực tế có thể chỉ là một vài chu kỳ trong 4 micro giây, nhưng mã của bạn sẽ báo cáo là 8.

Hãy thử thực hiện 10 hoặc 20 delayMicroseconds(4);cuộc gọi liên tiếp (bằng cách sao chép mã, không phải bằng cách sử dụng vòng lặp) và sau đó báo cáo kết quả micros().


Với 10 độ trễ của 4, tôi nhận được hỗn hợp gồm 32 và 28 ... nhưng 4 * 10 = 40.
bwoogie

Bạn nhận được gì với 10 sự chậm trễ của 5? :) Cũng lưu ý, đối với bitbanging, bạn có thể cần phải sử dụng các truy cập cổng khá trực tiếp, tức là không digitalWrite(), phải mất vài micro giây để thực thi.
James Waldby - jwpat7

40 & 44 ... Nhưng không nên làm tròn số? Tôi đang thiếu gì ở đây?
bwoogie

Bạn có ý nghĩa delayMicroseconds()gì không? Tôi không thấy điều đó tốt hơn là làm tròn xuống. Liên quan đến nguồn gốc của sự không chính xác, nếu thói quen được nội tuyến thì thời gian phụ thuộc vào mã xung quanh. Bạn có thể đọc danh sách lắp ráp hoặc tháo gỡ để xem. (Xem danh sách lắp ráp Lập danh sách Phần trong câu trả lời của tôi để đặt câu hỏi Tương đương cho PORTB trong Arduino Mega 2560 , có thể liên quan đến dự án bitbanging của bạn
James Waldby - jwpat7

2

Tôi đang kiểm tra xem Arduino chậm trễ đến mức nào ... Có vẻ như nó khá khủng khiếp với nó.

micros()có độ phân giải được ghi chép rõ ràng là 4 bản.

Bạn có thể cải thiện độ phân giải bằng cách thay đổi bộ đếm gộp cho Timer 0 (tất nhiên sẽ loại bỏ các số liệu, nhưng bạn có thể bù cho điều đó).

Hoặc sử dụng Timer 1 hoặc Timer 2 với bộ đếm trước 1, cung cấp cho bạn độ phân giải 62,5 ns.


 Serial.begin(9600);

Dù sao thì nó cũng sẽ chậm thôi.


8 4 8 4 4 4 4 4 8 8 8 8 4 8 8 4 4 8 4 4 4 8 8 8 4 8 4 4

Đầu ra của bạn hoàn toàn phù hợp với độ phân giải 4 Kết micros()hợp cùng với thực tế là đôi khi bạn sẽ nhận được hai "tích tắc" và đôi khi là một, tùy thuộc vào chính xác thời điểm bạn bắt đầu thời gian.


Mã của bạn là một ví dụ thú vị về lỗi đo lường. delayMicroseconds(4);sẽ trì hoãn gần 4 trận đấu. Tuy nhiên, nỗ lực của bạn để đo lường nó là có lỗi.

Ngoài ra, nếu một ngắt xảy ra thì nó sẽ kéo dài khoảng một chút. Bạn cần tắt ngắt nếu muốn độ trễ chính xác.


1

Khi đo bằng máy hiện sóng, tôi thấy rằng:

delayMicroseconds(0)= delayMicroseconds(1)= 4 s độ trễ thực.

Vì vậy, nếu bạn muốn độ trễ 35 giây, bạn cần:

delayMicroseconds(31);

0

Có bất cứ điều gì tôi có thể làm để có được kết quả chính xác, phù hợp?

Việc triển khai Arduino khá chung chung do đó có thể không hiệu quả trong một số ứng dụng. Có một vài cách cho sự chậm trễ ngắn, mỗi cách đều có những thiếu sót riêng.

  1. Sử dụng nop. Mỗi người là một chỉ dẫn thứ 16 của chúng tôi.

  2. Sử dụng tcnt0 trực tiếp. Mỗi cái là 4us khi bộ đếm gộp được đặt thành 64. Bạn có thể thay đổi người đặt trước để đạt được độ phân giải thứ 16 của chúng tôi.

  3. Sử dụng tick, bạn có thể thực hiện một bản sao của systick và sử dụng nó như là cơ sở của sự chậm trễ. Nó cung cấp độ phân giải tốt hơn cộng với độ chính xác.

biên tập:

Tôi đã sử dụng khối sau để tính thời gian cho các phương pháp khác nhau:

time0=TCNT0;
delay4us();             //65
//t0delayus(4*16);          //77
//_delay_us(4);             //65
//delayMicroseconds(4);     //45
time1=TCNT0 - time0;        //64 expected

trước đó, tôi đã thiết lập lại bộ đếm trước timer0 thành 1: 1 để mỗi dấu TCNT0 là 1/16 của một phần triệu giây.

  1. delay4us () được xây dựng từ NOP (); nó tạo ra độ trễ 65 tick, hoặc chỉ hơn 4us;
  2. t0delayus () được xây dựng từ độ trễ timer0. nó tạo ra độ trễ 77 tick; kém hơn một chút so với delay4us ()
  3. _delay_us () là hàm gcc-avr. hiệu suất ngang bằng với delay4us ();
  4. delayMicroseconds () tạo ra độ trễ 45 tick. cách arduino thực hiện các chức năng thời gian của nó, nó có xu hướng thiếu, trừ khi trong một môi trường có các ngắt khác.

hy vọng nó giúp.


Lưu ý rằng kết quả dự kiến ​​là 65 tick, không phải 64, vì việc đọc TCNT0mất 1 chu kỳ CPU.
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.