Могу ли я сделать задержку в микросекундах более точной?

8

Я пытаюсь укусить данные DMX, и это требует импульсов 4us. Не очень удачно с результатами, я проверяю, насколько хорош Arduino в задержке ... Кажется, это довольно ужасно.

Вот небольшой тест, который я сделал:

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
}

И результаты:

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

Я был шокирован, насколько это плохо. Это вдвое больше времени, которое я хотел отложить, но это даже не соответствует тому, где я мог бы просто разделить на 2!

Что я могу сделать, чтобы получить правильные, последовательные результаты?

bwoogie
источник
Почему вы не используете прямой доступ к UART?
Игнасио Васкес-Абрамс
Таким образом, я могу иметь более одного выхода.
bwoogie
Мега имеет четыре UART. Даже если вы постоянно держите один для программирования, он все равно дает вам три вселенных.
Игнасио Васкес-Абрамс
Я просто тестирую с мега, потому что это то, что у меня есть на данный момент, в финальном проекте будет
ATMEGA328
1
Проблема здесь в том, как вы измеряете.
Гербен

Ответы:

7

Как объяснялось в предыдущих ответах, ваша настоящая проблема не в точности delayMicroseconds(), а в разрешении micros().

Однако, чтобы ответить на ваш актуальный вопрос , есть более точная альтернатива delayMicroseconds(): функция _delay_us()из AVR-libc является точной по циклу и, например,

_delay_us(1.125);

делает именно то, что говорит. Главное предостережение в том, что аргумент должен быть константой времени компиляции. Вы должны для #include <util/delay.h>того, чтобы иметь доступ к этой функции.

Также обратите внимание, что вам нужно блокировать прерывания, если вы хотите точную задержку.

Изменить : В качестве примера, если бы я должен был генерировать импульс 4 мкс на PD2 (контакт 19 на Мега), я бы поступил следующим образом. Во-первых, обратите внимание, что следующий код

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

генерирует длительный импульс 0,125 мкс (2 такта ЦП), потому что это время, необходимое для выполнения инструкции, которая устанавливает порт LOW. Затем просто добавьте недостающее время в задержку:

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

и у вас есть тактовая ширина импульса. Стоит отметить, что этого нельзя достичь digitalWrite(), так как вызов этой функции занимает около 5 мкс.

Эдгар Бонет
источник
3

Ваши результаты теста вводят в заблуждение. delayMicroseconds()фактически задерживается довольно точно (для задержек более 2 или 3 микросекунд). Вы можете просмотреть его исходный код в файле /usr/share/arduino/hardware/arduino/cores/arduino/wiring.c (в системе Linux; или по аналогичному пути в других системах).

Тем не менее, разрешение micros()составляет четыре микросекунды. (См., Например, страницу garretlab оmicros() .) Следовательно, вы никогда не увидите показания от 4 до 8 микросекунд. Фактическая задержка может составлять всего несколько циклов в течение 4 микросекунд, но ваш код сообщит об этом как 8.

Попробуйте выполнить 10 или 20 delayMicroseconds(4);вызовов подряд (дублируя код, не используя цикл), а затем сообщите результат micros().

Джеймс Уолдби - jwpat7
источник
С 10 задержками по 4 я получаю смесь из 32 и 28 ... но 4 * 10 = 40.
bwoogie
Что вы получаете, скажем, с 10 задержками по 5? :) Также обратите внимание, что для бит-бэнгинга вам может понадобиться довольно прямой доступ к порту, т. Е. Нет digitalWrite(), для выполнения которого требуется несколько микросекунд.
Джеймс Уолдби - jwpat7
40 и 44 ... Но разве не должно быть округления? Что мне здесь не хватает?
bwoogie
Под «округлением» ты имеешь в виду delayMicroseconds()? Я не вижу в этом ничего лучшего, чем округление вниз. ¶ Что касается источника неточности, если подпрограмма становится встроенной, то время зависит от окружающего кода. Вы можете прочитать списки сборки или разборки, чтобы увидеть. (См. Раздел «Составление списков сборок» в моем ответе на вопрос « Эквивалент для PORTB» в Arduino Mega 2560 , который в любом случае может иметь отношение к вашему проекту бит-бинга
Джеймс Уолдби - jwpat7
2

Я проверяю, насколько хорош Arduino в задержке ... Кажется, это довольно ужасно.

micros()имеет хорошо документированное разрешение 4 мкс.

Вы можете улучшить разрешение, изменив прескалер на Таймер 0 (конечно, он выбрасывает цифры, но вы можете это компенсировать).

В качестве альтернативы используйте Таймер 1 или Таймер 2 с прескалером 1, который дает разрешение 62,5 нс.


 Serial.begin(9600);

Это будет медленно в любом случае.


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 мкс в micros()сочетании с тем фактом, что иногда вы получаете два «тика», а иногда один, в зависимости от того, когда именно вы начали отсчет времени.


Ваш код является интересным примером ошибки измерения. delayMicroseconds(4);будет задерживаться в течение около 4 мкс. Однако ваши попытки измерить его виноваты.

Кроме того, если произойдет прерывание, оно немного увеличит интервал. Вам нужно отключить прерывания, если вы хотите точную задержку.

Ник Гаммон
источник
1

При измерении с помощью осциллографа я обнаружил, что:

delayMicroseconds(0)= delayMicroseconds(1)= 4 мкс реальная задержка.

Итак, если вы хотите задержку 35 мкс, вам нужно:

delayMicroseconds(31);
сиги
источник
0

Что я могу сделать, чтобы получить правильные, последовательные результаты?

Реализация Arduino является довольно общей, поэтому может быть не такой эффективной в некоторых приложениях. Есть несколько способов для коротких задержек, каждый из которых имеет свои недостатки.

  1. Используйте нет. Каждый из них - одна инструкция, так что 16-е из нас.

  2. Используйте tcnt0 напрямую. Каждый из них имеет значение 4us, так как прескалер установлен на 64. Вы можете изменить прескейкер для достижения 16-тикратного разрешения.

  3. Используйте галочки, вы можете реализовать клон Systick и использовать его в качестве основы задержки. Он предлагает более высокое разрешение плюс точность.

редактировать:

Я использовал следующий блок для определения времени различных подходов:

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

до этого я сбрасывал прескалер таймера 0 на 1: 1, поэтому каждый тик TCNT0 составляет 1/16 микросекунды.

  1. delay4us () создается из NOP (); задержка составила 65 тиков или чуть более 4 мкс;
  2. t0delayus () создается из задержек timer0. задержка составила 77 тиков; немного хуже, чем delay4us ()
  3. _delay_us () - это функция gcc-avr. производительность наравне с delay4us ();
  4. delayMicroseconds () произвел задержку в 45 тиков. способ, которым arduino реализовал свои функции синхронизации, он склонен недооценивать, если только в среде нет других прерываний.

Надеюсь, поможет.

dannyf
источник
Обратите внимание, что ожидаемый результат составляет 65 тактов, а не 64, потому что чтение TCNT0занимает 1 цикл ЦП.
Эдгар Бонет