Есть ли более точная альтернатива micros()?
- Войдите на сайт для отправки комментариев
Чт, 24/11/2016 - 12:54
Функция micros() возвращает текущее значение микросекунд с точностью до 4-ех. Но для реализации задачи этого оказалось мало. Возможно ли как-то отсчитать время с точностью до 1мкс?
nano(); femto(); )))
Porosenok, любой аппаратный таймер ардуины может считать по 0,0625 µS , но "накладые расходы" на высокоуровневых языках столь велики, что точнее чем микрос вряд ли получится измерить. Только если писать на ассемблере.
Porosenok, любой аппаратный таймер ардуины может считать по 0,0625 µS , но "накладые расходы" на высокоуровневых языках столь велики, что точнее чем микрос вряд ли получится измерить. Только если писать на ассемблере.
А интересно - IDE ассембрерные вставки поддерживает?
И сам же отвечу, поддерживает, осталось изучить ассемблер )))
Возможно ли как-то отсчитать время с точностью до 1мкс?
Фух! Успел до Клапауция.. Можно! Я - разрешаю!
Фух! Успел до Клапауция.. Можно! Я - разрешаю!
Спасибо большое! Я обязательно воспользуюсь.
Возможно ли как-то отсчитать время с точностью до 1мкс?
Фух! Успел до Клапауция.. Можно! Я - разрешаю!
Разрешить мало! А подать идею - как?
Разрешить мало! А подать идею - как?
В смысле "как"???? Вы всерьез спрашиваете или просто с аглицким не все гладко, или даташит на кристал не скачивается??
Таймер 16битный запустили на 16МГц и его счетчик читаете. Если внешнее событие - то его на ICP пин ловить можно, если внутреннее, то просто в скобках cli/sti запомнить регистр счетчика.
Поправку на накладные расходы (они всегда в пределах 1 мкс) нужно учесть при отладке программы.
Функция micros() возвращает текущее значение микросекунд с точностью до 4-ех. Но для реализации задачи этого оказалось мало. Возможно ли как-то отсчитать время с точностью до 1мкс?
можно. Для этого делаем свою настройку таймера так, стобы один счет соотвествовал 1 мкс
но если мы говорим про ардуину с частотой кварца 16мгц то это получится, что между срабатываниями таймера будет всего 16 тактов. Это по сути всего лишь 16 машинных инструкций если исключить те что требуют несколько тактов. Этого от силы хватит на ассемблере чтобы увеличить значение счетчика и покинуть прерывание. На основную программу ресурсов не останется совсем.
Если вам нужна такая точность то нужно выбирать более производительные системы
Мне надо всего лишь в определённые моменты времени просто ждать прихода фронта с высокой точность. Теоретически же можно только на это время перепрограммировать таймер, а потом вернуть всё взад.
но если мы говорим про ардуину с частотой кварца 16мгц то это получится, что между срабатываниями таймера будет всего 16 тактов. Это по сути всего лишь 16 машинных инструкций если исключить те что требуют несколько тактов. Этого от силы хватит на ассемблере чтобы увеличить значение счетчика и покинуть прерывание. На основную программу ресурсов не останется совсем.
Тут не совсем ясно, зачем мне увеличивать значение счётчика. Таймер-то аппаратный.
Мне надо всего лишь в определённые моменты времени просто ждать прихода фронта с высокой точность. Теоретически же можно только на это время перепрограммировать таймер, а потом вернуть всё взад.
Насколько я помню дэйташит (а конкретно этот вопрос меня не интересовал), разработчиком специально предусмотрен такой режим совместной работы таймера и аппаратного прерывания. Соответственно, ожидаемое разрешение 62.5 нс.
Мне надо всего лишь в определённые моменты времени просто ждать прихода фронта с высокой точность. Теоретически же можно только на это время перепрограммировать таймер, а потом вернуть всё взад.
У меня есть пример вот почти того, что вам нужно. Считает длину импульса с точностью 3 такта. Тыц. Только переделывать под вас я не буду, очень лень :)
У меня есть пример вот почти того, что вам нужно. Считает длину импульса с точностью 3 такта. Тыц. Только переделывать под вас я не буду, очень лень :)
Спасибо. Примерно теперь представляю в какую сторону копать.
Мне надо всего лишь в определённые моменты времени просто ждать прихода фронта с высокой точность. Теоретически же можно только на это время перепрограммировать таймер, а потом вернуть всё взад.
Вам надо поймать одиночный импульс?
Всем спасибо, всё получилось. Гугл тоже немного помог. Удалось отследить приход импульса с точностью большей, чем микросекунда.
Ну и далее в лупе:
Porosenok, то, что вы привели не может ничего измерять, если предположить что 5 строка ловит конец импульса, то что ловит его начало? Это работало бы худо-бедно, если бы импульс начинался точно с момента выполнения команды TCNT2=0. Приведите полный код, может тут чего-то не хватает?
Добавил код передачи импульса. После отправки импульса жду ответ на него. Безусловно, вопросы вызывает строчка
Если она выполняется за время, меньшее чем 250нс, то ни на что не влияет. В противном случае она и будет определять точность.
Ардуиновские функции digitalRead (4-6 мкс), digitalWrite (6-8 мкс) и др. довольно медленные. Чтобы было быстрее - нужно прямое обращение к портам через регистры.
http://arduino.ru/forum/obshchii/vremya-vypolneniya-otdelnykh-komand-arduino
Porosenok, а как вы определили, что точность улучшилась? while будет выполняться примерно 3..4 µS на один егойный круг. Но это цветочки, ягодки будет если вы измеряете отрезки времени более, чем 0,0625*255=~ 16µS -таймер уйдёт в прерывание, и будет там "сидеть" порядка 15 µS. Но если перейти на 16 битный таймер, отказаться от прерываний (макс. время будет 0,0625 * 65535=4mS)и строчку while переписать вставкой на ассемблере, тогда да, точность реально вырастет. Думаю, можно будет "замахнуться" на 1uS
Дело в том, что данные получаются именно те, которые должны быть. Допускаю, что это случайность, но проверю. А так вы меня расстроили. digitalRead(), выполняющаяся так долго - это, честно говоря, вообще ни в какие ворота не лезет. Там всего-то надо считать данные с порта (2-4 такта)!
Значит буду делать вставку на ассемблере. Хотя всё больше в голову закрадывается крамольная мысль, написать вообще всё на ассемблере... Правда тогда и Ардуино не нужно в принципе... Хм...
Но если перейти на 16 битный таймер, отказаться от прерываний (макс. время будет 0,0625 * 65535=4mS)и строчку while переписать вставкой на ассемблере, тогда да, точность реально вырастет. Думаю, можно будет "замахнуться" на 1uS
16-битный таймер, это таймер1?
Думаю, можно будет "замахнуться" на 1uS
Вот тут надо разобраться основательно.
Команд получается три: чтение порта ввода-вывода, условный переход и чтение регистра таймера. Итого по тактам сколько набежать может?
Porosenok, 16-битный это да, таймер1. Подсчитать всё по тактам наперёд довольно трудно, для этого нужно сначала написать все команды) Кстати написать весь обработчик на ассемблере -хорошая мысль, 80% будущего кода уже есть в том примере, что я давал. Осталось дописать управление пином и задержку.
Porosenok Это же для УЗ дистанциометра? что-то во мне зажглось, и написал я этот скетч на асме. Вернее переделал мой же образец. Триггер 13 пин-выход , эхо 12 пин-вход. Проверил, работает. Компилить в IDE 1.6.8. Точность очень сложно оценить, тут же накладывается ещё ограничение по точности самого датчика, а он очень склонен к тому, что б гнать болтающиеся данные. Но на хорошей преграде, при точной соосности даже миллиметры стабилизирутся.
Дело в том, что данные получаются именно те, которые должны быть. Допускаю, что это случайность, но проверю. А так вы меня расстроили. digitalRead(), выполняющаяся так долго - это, честно говоря, вообще ни в какие ворота не лезет. Там всего-то надо считать данные с порта (2-4 такта)!
Значит буду делать вставку на ассемблере. Хотя всё больше в голову закрадывается крамольная мысль, написать вообще всё на ассемблере... Правда тогда и Ардуино не нужно в принципе... Хм...
Тогда пишите в лоб простую программу
и будет там всего три джампа
(на ассемблере ес-но)
первый джам Дмитрия )))
"wait_start:"
"\n\t"
38
"sbis 0x3,4"
"\n\t"
39
"rjmp wait_start"
"\n\t"
Porosenok Это же для УЗ дистанциометра?
Ну, не совсем. Это будет уз анемометр. За скетч спасибо. Посмотрю...
почитайте в даташите про Input Capture Unit
для атмега 328 об этом написано в разделе описания Timer1
это аппаратный способ посчитать время по внешнему сигналу
Вот ровно все, что написано в крайних 10-ти сообщениях я написал в своем, у Димы пример - тоже хороший.
Все так меня не любят, что не прочитали? И про 16 бит и про ICP.
Толи расстраиваццо, толи гордиццо... вотпрям не знаю... хотя, все же, обидно.
---------------------
Поросенок! Внешнее событие ловите на ICP, ничего точнее и быстрее не найдете. Вариант с "while(digitaRead())" - не выдерживает никакой критики.
С ICP конечно лучше, чем с while :) Но тут тоже не всё так гладко, ICP хорош в работе с прерываниями. Без прерывания он не даёт преимуществ перед простым счётным регистром TCNT. А прерывания - хана точности. В скетче на асме от 0 до 3 тактов уходит в жернова цикла-опроса фронтов импульса . Т.е. можно потерять в алгоритме от 0 до 6 тактов , это чистая девиация результата. (По факту почему-то не больше трёх биения) + 2 такта чистых потерь на старт/стоп таймеров. Эти два такта можно прибавить к результату как константу. С прерываниями об таких малых потерях можно и не мечтать..
Дмитрий, по коду должно получиться байт 150-200, а скомпилированный 3500, шо там компилятор насобирал?
ua6em, там два жирбасика - float и Serial() :)
dimax чем input capture страдает в точности? Прерывание возникает по завершению счета, т.е. Точность измерения зависит только от точности кварца и никак не зависит от кода
axill, ну да, вы конечно правы! Сейчас подумал -да, будет точнее чем ассемблером. Практически точность в один такт получится при идеальном кварце. Но есть сильный нюанс -вход в прерывание в автоматически сгенерированном коде ардуины занимает порядка 7 микросекунд, а в прерывании нам нужно успеть обнулить или переписать регистр ICP в свою переменную, поменять бит ICES, что б ловить другой фронт. В общем этой мышиной возьни ещё на десяток тактов. Итого имеем гарантированную длину импульса, которую можно измерить с помощью ICP -где то от 50 тактов МК, (эта цифра не с потолка, я получал её экспериментально) иначе велик риск пропустить второй фронт импульса. Ну и конечно ограничение в длине импульса -65536 тактов без использования дополнительных перрываний. А так да, самый идеальный вариант по качеству.
Добавлено: ещё мы не рассмотрели вариант срабатывания другого прерывания прямо перед ICP. В этом случае наше прерывание оставит свой флажок, и обработчик будет ждать пока кончится предыдущее прерывание. А тут подойдёт новый фронт импульса, и освежит значение ICP. Стало быть обязательно нужно отключать все прерывания кроме кэпчура, иначе результат не гарантирован при любой длине импульса.
Дима! Это УЗ анемометр (как сказал ТС).
То есть, если излучатель и приемник поставить на расстоянии в 1000 мкс в спокойном воздухе (примерно 33 см), то ЛЮБЫЕ задержки, по определению безразличны.
Важна лишь разница замеров в спокойном и движущемся воздухе, а она, разница, никак не ухудшится от накладных расходов на входы в прерывания. На время замеров можно остановить Таймер0, чтобы даже учет миллис нам не мешал. А измерять на Таймер1 - 16бит.
Разрешение в 1 такт даст точность анемометра в (примерно) 2 см/сек, а если излучатель и приемник разнести на 66 см, то и точность станет в 1 см/сек.
Усреднить по серии в 10-20 замеров, пачками по 4-5, с отбрасыванием мин и макс, вообще не прибор, а конфетка получится! Хоть в поверку сдавай!
Покумекал, и написал точный измеритель длины восходящего импульса на ICP, но без прерывания, код для меги328. Пригодится как заготовка для каких нибудь проектов. Метод сохраняет все достоинства ICP, и позволяет измерять более короткие импульсы, нежели с прерыванием. Минимальная длина входящего импульса -14 тактов МК (875nS) . Максимальная 65535 тактов (4mS) Точность измерения 1 такт МК (62,5 nS) Недостатки -метод не позволяет определить что импульс был короче или длиннее своего диапазона, и выдаст левые цифры если диапазон был нарушен. На 8 пин подать импульс, функция отдаёт длину импульса в тактах МК. Компилировать в arduino 1.6.8, в других IDE работоспособность не гарантируется.
dimax, в встроенный Ассемблер разве не признает имен регистров, только номера?
andriano, ну да, только регистры. Вроде все внутренние дефайны в avr-gcc сделаны для СИ. По идее можно сделать перед телом кода шапку дефайнов в асм-формате.
Что будет если импульс вобще не прийдет?
Значит, Ардуина будет ждать столько, сколько нужно.
Это да, программа должна 2 раза поймать флаг ICF, таймаута нет. Нет смысла его вносить, -увеличиться обработчик, если таймаут нужен -тогда наверное удобнее просто использовать вариант с прерыванием. Правда есть и компромиссный вариант -включить вотчдог :)
В скетче из поста #23 функция всегда возвращает 0. Причём даже если в конце в r25- r22 загрузить любые константы, всё равно возвращает ноль
Porosenok, не знаю чем помочь, у меня всё работает. По симптому похоже на несоблюдение рекомендации компилить в IDE 1.6.8
Да оно, в принципе, не страшно. Значения можно и после возвращения из функции с таймеров взять. Тут другие проблемы всплыли. Почти в половине случаев вызова функции при первом же опросе 12-ого вывода получаю ноль, ну и сразу выход из цикла. Хотя на осциллограммах всё чисто. Пока никакой закономерности не выявил.
Porosenok, эээ.. "карты не врут" :) Раз ноль -значит импульс был, и он был менее 6 или 7 тактов, не помню сколько там длится опрос. И кстати замечу, мой алгоритм начинает отсчёт времени по прибытию "единицы", а из вашего наброска следовало, что прибытие первой единицы уже завершает счёт.
Ну да, карты не врали. Это оказались аппаратные проблемы с помехами от импульсного источника питания компьютера. При подключении осциллографа с общей с компьютером землёй они пропадали.
Вроде всё получилось. Погрешность обнаружения фронта по вашему скетчу выходит в 3 такта, т.е. около 200нс. Этого более чем достаточно. Дело в том, что абсолютные измерения длительности прохождения сигнала от излучателя к приёмнику не нужны, да они и бессмысленны, т.к. значения очень сильно зависят от мгновенного значения температуры. Обнаружение начала импулься тоже не нужно, что видно из осциллограммы. Я провожу два измерения: "туда" и "обратно", т.е. относительные измерения. Разница во времени прохождении сигналов уже очень слабо зависит от температуры и зависит только от скорости ветра.
Как выяснилось, даже 200нс - это излишняя точность. К "софтовым" 200нс приходится добавлять около 600нс "железных" - результат шума микрофонного усилителя.
dimax, а почему не оформить в виде обработчика прерывания по захвату таймера? Тогда, если подключить прерывание по переполнению можно измерять бОльшие интервалы. Да и код не будет "тормозить" если импульс не придет вовсе .. можно тупо сделать глобальный флаг "замер завершен" и по нему в программе подсматривать за прерыванием. :)
Timer/counter capture event очень удобно.
Если не секрет, для чего нужна такая точность при измерении скорости ветра?
Если это, конечно, ветер, а не аэродинамическая труба для продувки моделей ;)
Если хочется странного, чота идет не так.
Покумекал, и написал точный измеритель длины восходящего импульса на ICP
И как его применять???
dimax, а почему не оформить в виде обработчика прерывания по захвату таймера?
Я думал. Если сделать обычное прерывание, то компилятор сначала напихает туда команд сохранения всех рабочих регистров в стек, это сразу 15-20 тактов МК коту под хвост. Этого нельзя допустить. Значит надо выполнить ISR_NAKED (), сохранить по самому минимуму в стек, затем сохранить куда-то 16 бит данных из регистра ICR, перепрограммировать таймер на ловлю противоположного ипульса, восттановить сохраннёные регистры из стека и можно выходить. Просто на обычной ассемблерной вставке это скорее всего не сделать, придёться цеплять внешний ассемблерный файл, который подключать к проекту. В общем довольно муторно, конечно была бы какая то серьёзная надобность, можно б было и написать..
И как его применять???
Подать на вход ICP (пин8) импульс, функция выплюнет его длительность в тактах Мк
Как функцию вызвать? Что-то не пойму, пишу так - tau=asm_func(); OK?