Timers Capture mode или ПРАВИЛЬНАЯ работа с таймером в режиме захвата

AVR
Сказано об этом в сети много, но на примере дремучих камней и без особых разъяснений. По этому напишу своё.
Есть у АВР такая фишка — захват. Суть в том, что по фронту или спаду (настраивается битом) сигнала на специальной ноге ICRx, делается снимок регистра TCNT и сохраняется в bmp формате и копируется в регистр ICRx. Опосля чего вызывается прерывание, если оно разрешено. В прерывание мы можем входить не спеша — данные из регистра никуда не денутся. Главное успеть обработать до следующего «снимка».
Всё красиво, всё хорошо. Но вот беда. Атмельцы не сделали возможность сбрасывать/запускать таймер по событию! Таймер крутится в цикле, снимается его мгновенное значение, а дальше делай с ним что хочешь.
Самый секас начинается, когда нам надо отслеживать опускание частоты сигнала ниже порога переполнения таймера. Был бы аппаратный ресет — всё понятно. Мониторь бит OVF при считывании результата и не парься! Но, увы и ах.

Бороться с сим можно двумя способами.
1. Тупой. Применяется во всех статьях в инете и даже в атмельской апноте www.atmel.com/Images/doc2505.pdf. Суть метода в том, что при каждом входе в ICR прерывание мы сбрасываем счётчик софтварно:
void interrupt [TIMER1_CAPT1_vect] ISR_ICP1(void) {
// read high byte from Input Capture Register (read 16 bit value and shift it eight bits to the right)
PORTB = ~( ICR1>>8); // Invert Byte 
TCNT1 = 0; // Reset Timer1 Count Register
}

Много думал… Вход в прерывание, это тактов 10-20. + накладные расходы. + задержка входа от того, что мы сейчас сидим в другом прерывании. Ну и нафига такая аппаратная капча, которая даёт не прогнозируемую и довольно приличную СОФТОВУЮ ошибку? Это надругательство над самой идеей капчи! Особенно если у нас предделитель 1 и каждый такт имеет значение.
2. Путь джедая. Таймер крутится по кругу, мы делаем «снимки» и обрабатываем их. Проблема переполнений, пропаданий сигнала, фильтрации решается софтварно. точность измерения не зависит от загрузки МК, если, конечно, мы успеваем обрабатывать результат до следующего изменения сигнала на ноге.
Предлагаю сообществу готовый код. Написан для ATmega640 под IAR6. Работают все 4 16-и битных таймера в режиме измерения периода от спада до спада (полный период). Протестирован в железе на кварце 16 МГц. Прерывания выглядят громоздко, но выполняются в среднем за 10 мкс. Для предделителя таймера == 8 лагов нет, на 1 не проверял. Гонял на частотах от 30 до 1150 Гц, больше смысла не было по ТЗ.
Возможности.
1. Точность измерения периода не зависит от времени входа/выхода в прерывание.
2. Отслеживается опускание частоты ниже допустимого уровня (переполнение). В этом случае на выход идёт 0.
3. Отслеживание резкого пропадания сигнала (обрыв, резкое отключение). В этом случае в регистрах остаётся мусор, который гонится на выход. Через OVF_LIMIT переполнений счётного таймера этот мусор принудительно будет перезаписан нулями.
4. Фильтрация усреднением выборок. Число выборок задаётся дефайном TIMERS_FILTER_LIMIT и рекомендуется быть кратным 2.

Файл содержит всего две функции, которые должны торчать наружу. Это
mcu_timers_init();

и
mcu_timers_manager();

Пользовать в мейне их следует стандартно:

extern void mcu_timers_init(void);
extern void mcu_timers_manager(void);

void main( void ) {
  mcu_timers_init();
  __enable_interrupt(); 
  
   do  { 
     mcu_timers_manager();
  } while(1); 
}

Строчка
Output_Buffer[writing_layer].timer4_result = (unsigned int)t4_filter_buffer;

Копирует у меня результат в выходной фрейм. У вас тут может быть что-то своё. В остальном, правильно собранный код в настройке и наладке не нуждается :)
  • +2
  • 04 января 2013, 19:47
  • Dikoy
  • 1
Файлы в топике: Timers.zip

Комментарии (63)

RSS свернуть / развернуть
Ну и нафига такая аппаратная капча, которая даёт не прогнозируемую и довольно приличную СОФТОВУЮ ошибку?
… так счетчик же продолжает тикать… и при входе в прерывание вы можете узнать сколько он натикал и скомпенсировать следующий результат?
0
Если счётчик продолжает тикать, ничего компенсировать не надо. Лишние вычисления, от которых будет только вред.
Если обнуляем — как вариант. Но тогда «нафига такая капча?» (с)
0
Если счётчик продолжает тикать, ничего компенсировать не надо. Лишние вычисления, от которых будет только вред.
… предположем произошла «капча» и из счетчика в ICR загрузилось некое значение… далее
Вход в прерывание, это тактов 10-20. + накладные расходы. + задержка входа от того, что мы сейчас сидим в другом прерывании
… а счетчик в это время тикает. После того как вы попали в обработчик прерывания вам всего-лишь надо оставить в счетчике эти временные затраты
TCNTx -= ICRx;
+1
Во-1 компилер такое не пропустит. Оба регистра volatile. Да и всё равно копирование будет проходить через регистры ядра.
Во-2, допустим, делитель 1. Один такт МК == 1 тик таймера. Сколько тактов надо чтоб вычитать 2 регистра и вычесть?
Если же делитель != 1, то от куда мы знаем сколько насчитал предделитель к этому моменту? минимум 1 такт таймера в пролёте для любого делителя.
Помнится, видел код, где по переполнению таймера вызывался обработчик, в котором запускался АЦП и крутился while до конца преобразования. Вот все методы с вхождениями/вычислениями ему родственны.
Есть капча — захват значения на одном такте. ВСЁ. Никаких TCNT, забудьте про него. Все остальные методы, это обход аппаратного блока софтовыми методами с целью получения не прогнозируемой ошибки.
Проще тогда сделать вход по INT и снимать TCNT, забыв про капчу.
Кстати, в Xmega аппаратный сброс есть.
0
вы что-то фантазируете. У вас, сами пишите, частота 1.5кГц, и вы ещё трясётесь над
«минимум 1 такт таймера в пролёте»
.
Как предыдущий чел написал, никаких проблем скомпенсировать по текущему счётчику тоже нет.
+1
>>Во-1 компилер такое не пропустит. Оба регистра volatile.
Пропустит, но предупреждение выдаст (а может и не выдаст). И не более.
0
Даже предупреждения не выдаст. Да и с чего его выдавать? Выражение TCNTx = TCNTx — ICRx вполне корректно.
0
Нет. Атрибут volatile подразумевает атомарную операцию. Выполнить одновременно две атомарные операции компилер не может, во этому возопит, что регистры не могут быть извлечены одновременно, по этому он сделал порядок извлечения по своему разумению.
Цитата из ИАРа:
Warning[Pa082]: undefined behavior: the order of volatile accesses is
          undefined in this statement
   \   0000000C   9100....           LDS     R16, _A_ICR3
   \   00000010   9110....           LDS     R17, (_A_ICR3 + 1)
   \   00000014   9120....           LDS     R18, _A_TCNT3
   \   00000018   9130....           LDS     R19, (_A_TCNT3 + 1)
   \   0000001C   1B20               SUB     R18, R16
   \   0000001E   0B31               SBC     R19, R17
   \   00000020   9330....           STS     (_A_TCNT3 + 1), R19
   \   00000024   9320....           STS     _A_TCNT3, R18
0
Атрибут volatile подразумевает атомарную операцию
Да ни разу. Он подразумевает только одно — нельзя кэшировать/оптимизировать доступ к этой переменной. Атомарность понятие совершенно ортогональное и обеспечивать ее надо отдельно.
Цитата из ИАРа:
А GCC спокойно скомпилировал. Да и IAR только предупреждением плюнул.
0
Через копирование в промежуточный регистр — да. О том ИАР и предупреждает (текст предупреждения в моём топике) и о том ГЦЦ стыдливо умолчал, но сделал точно так же.
0
RISC-процессоры никак иначе с переменными и не могут работать. volatile означает только то, что код TCNT0+TCNT0 скомпилируется в LDS R0, TCNT0; LDS R1, TCNT0; ADD R0, R1, а не LDS R0, TCNT0; ADD R0, R0. IAR видимо заботится о том, чтобы следить за свойственными человеку ошибками (и это правильно) и предупреждает о том, что порядок чтения volatile переменных может иметь значение, но в данной конструкции не определен.
0
Козе понятно, что вычисления проводятся через регистры. Вопрос — какими командами и в каком порядке.
В порядке, выбранном ИАРом, мы получаем ошибку смещения таймера «всего» 8 тиков. А вот если TCNT вычитать первым (с точки зрения логики выполнения и представленного асма это не будет нарушением, т.к. оба регистра копируются в регистры процессора ДО выполнения операции и порядок их копирования не важен), то ещё +6 тактов.
Вот я grand1987 на это и указал. И его код лучше заменить на
u8_temp = ICRx;
TCNTx -= u8_temp;
тогда разночтения невозможны на любом компиляторе, но ошибка вычисления периода будет и будет больше теоретического предела в 1 такт для такого метода. Что, в моём понимании, не имеет смысла при использовании аппаратной капчи. Лишь если хочется создать себе проблему и потом героически её преодолевать… :-/
0
Ну, лично меня интересовал только пункт
Во-1 компилер такое не пропустит. Оба регистра volatile.
0
Имелось ввиду, что вычитание TCNT из ICR не будет выполнено «в одну строчку» так же красиво, как это выглядит на Си. Под этим будет целый набор АССМ команд, который похерит всю идею коррекции периода вычитаем мгновенного значения TCNT, которую тут некоторые продвигали, т.к. за время вычисления счётчик утикает ещё дальше, чем за время входа в прерывание, а прерывание обрастёт лишними вычислениями и потеряет весь цимес быстроты, по сравнению с моими. Как я сказал сразу «Лишние вычисления, от которых будет только вред.»
Ну и вдобавок варнинг вылезет, который будет нервировать.
Возможно, не чётко выразился.
0
Ну, на гцц варнинг не вылезет и в принципе метод применим с прескалером больше, чем требуемое на TCNT -= ICR количество тактов. Но в целом разумней всего оставить счетчик крутиться сам по себе и захватывать его состояния. Промежуток между ними можно определить простым вычитанием.
0
Не простым ;)
(sample — last_sample) & 0xFFFF;
0
А если за период между захватами таймер переполнился раз так с десяток? Либо наоборот, если я уверен, что все захваты пройдут за один цикл таймера? В первом разумнее вычитать значения таймера большей разрядности (ovf_count:tcnt), во втором взятие модуля не нужно. Ну и тащемта большинство случаев сводятся к этим вариантам. Твой прокатывает только если захватывать нужно постоянно и период захватов гарантированно меньше цикла таймера (зато позволяет в этом случае забить на счетчик переполнений).
0
Для этого и нужен счётчик переполнений, который у меня есть и работает очень быстро.
И он прокатывает на любом числе переполнений, от 1 до 16. причём 16 ограничено искусственно и выше он просто физически не залезет. И выдаёт при этом стабильный 0, пока сигнал вновь не войдёт в рамки измерений. А как только войдёт, сразу пойдут корректные данные.
Что мне и было надо.

Чтобы всё работало, как хочет социум, должен быть аппаратный старт/сброс таймера по капче, которого в AVR нет.
Тогда да, всё сведётся к
if( !(STATUS_REG & OVF_FLAG)) u16_period = TCNT;
else u16_period = 0;

Но мы живём в жестоком мире. А потому приходится адаптироваться.
0
Это дела не меняет. «Не простое» вычитание (sample — last_sample) & 0xFFFF — всего лишь решение для твоего варианта задачи, а не решение вообще. Да и в принципе является подмножеством того «простого вычитания», о котором говорил я.
0
Не совсем.
Чтобы фильтр работал корректно, нужно N последовательных выборок. И последовательность тут очень важна.
Если увеличивать разрядность за счёт учёта переполнений, мы получаем ряд неопределённостей, которые я описал ниже.
В любом случае, на моих частотах я получаю 11 бит на ВП и мне нет смысла учитывать переполнения. А если кому надо по другому, то это его проблемы :) Я не стремился дать панацею.
0
Точнее, u16_temp, конечно…
0
Дон Кихот и Колумб в одном лице…
+1
Ну так снести весь курс по авр нахЪ, раз все всё знают.
-1
Здравствуйте. Почему Вы так боитесь переполнений таймера? Считаете число переполнений (тем более, такая переменная уже есть), сравниваете, например, с числом 31 (пограничное число переполнений 65'536*31=2'031'616, при превышении которого принимается решение об отсутствии вращения), вводите его в выражение для расчета с весом 65536!!! и получаете частоту вращения (как я понял пример кода приведен для тахометра), но уже не 30,5*60/1,2~1500 об/мин, а ~50 об/мин с разрешением не хуже 0,01 об/мин.
+2
  • avatar
  • akl
  • 05 января 2013, 09:00
+1.
А как сказано «ПРАВИЛЬНЫЙ» способ — фтопку! Автору — за парту:
if(sample >= last_sample) { // вычисляем период сигнала
temp_result = (sample — last_sample);
} else {
temp_result=((0xFFFF — last_sample) + sample); // last_sample=FFFF, sample=0 ==> ERROR!
}
-1
Кстати ещё очень важно, что приоритет прерывания захвата выше прерывания переполнения таймера. И если между захватами происходит много переполнений таймера, которые считаются обработчиком прерывания переполнения, то если захватился 0, сработало прерывание захвата, а прерывание переполнения ещё не исполнилось, надо вводить коррекцию результата на 65536 отсчётов.
0
Кстати ещё очень важно, что приоритет прерывания захвата выше прерывания переполнения таймера.
Очень вам поможет приоритет, если вложенные прерывания не поддерживаются?
0
Сфига это оно не поддерживается? IAR по умолчанию 16 уровней вложения даёт, включать только надо уметь ;)
-1
>>Сфига это оно не поддерживается
Вложенные прерывания допустимы конечно. Но система приоритетов у авр примитивна — работает только при одновременно возникших запросах. И настроить уровень как надо невозможно.

>>IAR по умолчанию 16 уровней вложения даёт, включать только надо уметь
Да пофиг что у него там по умолчанию — настроить стек не проблема.
0
Нет. Если вы в теле обработчика прерывания выполняете SEI (там всё несколько сложнее, на электрониксе целая тема по разным способам, но я опишу упрощённо) то новое прерывание с этого момента его может перебить, если возникнет во время обработки или уже возникло на момент разрешения. И возврат будет в тело перебитого прерывания, а после его завершения — уже в мейнлуп.
По умолчанию в настройках ИАР указана максимальная глубина переходов 16. Можно изменить.
Так что вложение в АВР вполне работоспособно, хоть и более мудрёно полностью аппаратных систем.
-1
Соответственно, если вы разрешаете перебить, например, INT0, то компаратор будет срабатывать с приоритетом.
Это, конечно, костыль, но при отсутствии злоупотребления вполне рабочий.
-1
Да, непредсказуемая латентность запуска обработчиков при невозможности остановить счет — самая большая проблема при нескольких каналах измерений. Думаю, ее можно решить только поллингом комбинаций флагов, находясь в основной программе, вовсе отказавшись от прерываний. А может, кто-то знает красивое решение?
0
Описываемый Вами случай должен выполниться без ошибок, т. к. занесение в ICR и установка флага ICF происходит на аппаратном уровне, но! уже после переполнения [у меня, например, обработчик переполнения — это две команды INC или ADIW (в особо длинных периодах) и RETI]. Далее, см. ниже.
0
Упс. Извините. Это относилось к сообщению Vladimir_
0
Не, ну вот случай: сработал запрос ICR, а одновременно с ним, или во время входа в прерывание (пусть 10 циклов, а счетчик все работает) сработал OVF. Обработчику что делать? Не учитывать? А вдруг они вместе пришли и ICR «переприоритетил» OVF и надо прибавлять 65536? Просто +65536? А вдруг OVF на такт позже пришел, и его вклад не надо учитывать?
Это на одном канале измерения. А когда их параллельно три — тогда вообще весело. Там обработчики по закону подлости встанут так, что не поймешь какой из запросов (ICR млм OVF) у какого канала пришел рашьше.
0
Да, я хотел именно об этом написать. Но что-то глюкнуло. Самый интересный случай — это одновременный приход по окончании времени измерения ICF и OVF. Внутри времени измерения они обработаются правильно.
Если флаг ICF взведен, допустим за -5 тактов до OVF, т.е. в ICR аппаратно занесётся 0xFFFB и время измерения уже закончилось, то приход OVF уже неинтересен.
Если флаг ICF взведен, допустим после 5 тактов OVF, т.е. в ICR аппаратно занесётся 0x0005 и время измерения уже закончилось, то уже идет обработка OVF и в результате будет инкрементирован счетчик переполнений, т.е. результат будет правильный.
Если флаг ICF взведен одновременно с OVF, т.е. в ICR аппаратно занесётся 0x0000 и, за счет приоритета, контроллер уйдет на обработку «захвата», то нужно добавить 65536.
Я, например, при выходе из измерения для исключения такой ситуации проверяю флаг OVF и если он взведен измерение не останавливаю, а дожидаюсь еще одного периода измеряемой частоты, хотя и осознаю, что есть опасность срыва измерения при полном синхронизме тактовой и измеряемой частот.
0
Да, именно про это я написал. Я контролирую флаг прерывания по переполнению в случае захвата 0 в обработчике прерывания захвата. Получается, что при каждом захвате я записываю текущее значение захвата, текущее значение количества переполнений и флаг, что прерывание захвата обработалось до прерывания переполнения. А в прерывании переполнения уже эти значения копирую через необходимые промежутки времени для начала и конца периода отсчёта (тут же и число переполнений таймера увеличивается). Получается 3 переменных для начала периода, 3 для конца. Далее при обработке если в конечных измерениях есть флаг одновременности прерываний, то число переполнений увеличиваем на 1, если в начальных измерениях, то уменьшаем на 1. Вроде такой алгоритм работает правильно.
0
Я ошибся, вот что значит первого января пытаться разобраться в прошивке. Не правильно контролировать флаг переполнения только в случае захвата нуля, так как если произошло переполнение и захват в момент обработки другого прерывания, то захват может быть и не 0 для проблемного случая. В ближайшие время исправлю это и отработаю, тогда и напишу как в итоге сделал.
0
В таком случае рухнет в первую очередь фильтр, который уже не будет выхватывать N последовательных выборок. Всё остальное уже частности.
0
А вот если теперь то же самое, но уже с включённым мозгом?
Когда может быть такая ситуация?
1. Если частота поднялась до того, что 1 тик таймера стал равен периоду. В моём случае это анриал. Даже если случится, то уже пофиг, ибо турбину порвёт.
2. Если период равен переполнению. В этом случае и так на выход идёт ноль.
Учитесь смотреть на собаку, а не на блоху на собаке. (с)
0
Если же равенство так волнует, то классика:
(sample — last_sample) & 0xFFFF;
Но в моём случае сие не возможно, т.к. я вывожу диагностические сигналы в каждом случае (из приведённого кода выдернуты). И мне нужно отслеживать момент sample < last_sample. По этому только if, увы.
0
Меня не интересуют обороты ниже 5%. Более того, по логике работы системы мне даже полезно, что ниже 5% выдаётся 0. И мне важно, чтоб 0 выдавался сразу, а не спустя Х переполнений.
Спустя Х допустимо лишь в случае аварии, вероятность которой низка.
А точность и так получается почти 11 бит на верхнем пределе.
0
Таймер сбрасывать? Зачем если нужна дельта? Не хватает разрядов таймера — легко расширить просто инкрементом переменной-счетчика, необходимой разрядности при переполнении таймера.
0
Честно долго пытался понять собственно с чем идет борьба и какую проблему пытается решить автор. Так и не понял.
Объясните, чем плох стандартный подход? Никакого «секаса» с переполнением нет, бит OVF мониторим, код простой и очевидный:
uint16_t t1_ovf;
uint16_t prev_icr1;

ISR(TIMER1_OVF_vect)
{
  t1_ovf++;
}

ISR(TIMER1_CAPT_vect)
{
  uint32_t delta;
  uint16_t icr1 = ICR1;

  /* should we handle pended overflow right now? */
  if ((TIFR & 1<<TOV1) && !(icr1 & 0x8000)) {
    TIFR |= 1<<TOV1;
    t1_ovf++;
  }

  delta = ((1ul<<16) * t1_ovf) + icr1 - prev_icr1;

  t1_ovf = 0;
  prev_icr1 = icr1;

  /* do something with delta
     ....
   */
}
0
А теперь посмотрите на мой код и найдите 5 отличий.
Я их назову:
1) вы мониторите TOV1 в теле. Зачем? Приоритет OVF выше, значит если OVF произойдёт до или одновременно с капчей, обработчик уйдёт туда и выполнит инкремент, а если после капчи — то в текущем ISR(TIMER1_CAPT_vect) нас оно не волнует, т.к. к текущей капче отношения не имеет. То есть имеем нехилый набор лишних операторов.
2) Вычисление дельты намного жирнее простого (sample — last_sample) & 0xFFFF и даже вычисления через IF, которое использую я по необходимости. Пусть даже оно учитывает переполнения и расширяет разрядность (от чего только неопределённость, о которой ниже).
3) нет выдачи нуля при обрыве и снижении частоты до заданного уровня. Стрелка просто залипнет.
4) нет контроля переполнения delta. Если датчик оборвут или частота станет слишком низкой, результат вполне может перевалить за 32 бита и получим на выходе бурду.
5) ну и без оного контроля реализовать фильтр невозможно т.к. мы не знаем, уместятся ли N выборок в переменную заданной разрядности, а без этого знания мы опять получаем неопределённость, которая выльется в мину, которая неизвестно когда сработает.
Для домашнего использования пойдёт. Для системы контроля это недопустимо.

В остальном код 1:1 с моим.

А по поводу борьбы — народ возопил, что всё можно решить проще. В процессе обсуждения до некоторых дошло, до некоторых — нет… В любом случае проще никто не написал, ограничились трёпом :)
0
1) смотрим в datasheet по atmega640
17 $0020 TIMER1 CAPT Timer/Counter1 Capture Event
21 $0028 TIMER1 OVF Timer/Counter1 Overflow

у capt приоритет выше.

2) что значит жирнее? где критерии? так как в любом из подходов есть потолок частоты (выше частоты самого кварца все равно не прыгнешь), и никаких четких рамок изначально не задавлось, то считаю потерю тактов на «жирность» допустимой.

3, 4, 5) переполнение t1_ovf, «выдача нуля при обрыве» — это логика вашей конкретной задачи, которую можно добавить простой веткой кода if.

вы начинаете объяснение со слов «Но вот беда. Атмельцы не сделали возможность сбрасывать/запускать таймер по событию!», "… Ну и нафига такая аппаратная капча, которая даёт не прогнозируемую и довольно приличную СОФТОВУЮ ошибку".

я приготовился услышать (увидеть) попытку решить описываемую проблему, которой, как оказалось, просто нет.

так вот лично мне по-прежнему (и как я понял из комментариев многим другим) не понятно, в чем суть?
я простой стандартной тряпкой кода показал, что никакой софтовой ошибки нет, что сброс таймера не нужен и лишний огород городить в общем-то тоже не нужно. а вы продолжаете настаивать на какой-то дополнительной логике вашей задачи, известной только вам: «Меня не интересуют обороты ниже 5%. Более того, по логике работы системы мне даже полезно, что ниже 5% выдаётся 0. И мне важно, чтоб 0 выдавался сразу, а не спустя Х переполнений.».

ну так и добавьте эту логику в «Тупой спобоб №1», будет ровно то же самое. тогда собственно зачем «Путь джедая»?
0
1) Ну да, с приоритетом я ошибся. Смотрел 4-5 таймеры, там они рядом.
2) если что-то можно сделать быстро, зачем это делать медленно? В моём коде переполнением считается переполнение 2 и более. Как результат, мне не нужно проверять регистр, не нужно сравнивать 16-и битные переменные (всё volatile между прочим). У меня это делается ОДНИМ сравнением в 8 битах. Если было одно переполнение или меньше — логику капчи разрулит вычитание. Если было 2 или больше переполнений — значит вышли из берегов. Всё то же самое, но быстрее и прозрачней ИМХО.
3,4,5) я писал тахометр, для него это не частный случай ;) Вот, можете посмотреть на результат работы модуля ADAM-4080. Солидная фирма, стОит под 300 баксов. Подаю частоту с эталонного генератора и вот вам результат: narod.ru/disk/65470482001.87de0ad6fb455ee6c2c7ad6ad999ad5d/rotacion_video.zip.html Показания плавают, и плавают не хило. Хотя частота стоит как влитая. До пятого знака после запятой.
Файлы в *.exe т.к. это флеш видео, упакованное сразу в проигрыватель, а не вирусня ;) Ну как масяня в своё время :)

>я приготовился услышать (увидеть) попытку решить описываемую проблему, которой, как оказалось, просто нет.
Та ви шо! А откройте апноту из статьи и посмотрите, как там сделано. Или вот:
chipenable.ru/index.php/programming-avr/item/51-uchebnyy-kurs-16-razryadnyy-taymer-schetchik-t1-preryvanie-po-sobytiyu-zahvat-prostoy-chastotomer-na-avr.html
avrproject.ru/publ/capture_timer1_avr/1-1-0-24
Все примеры, что мне удалось найти, работают через Ж. Простой стандартной тряпки, вроде вашей, я не нашёл. Свой код сочинил сам, единолично. Если он совпадает с вашим, пусть и частично — гуд. Значит мысли идут в одном направлении :)
А если делать так, как в приведённых примерах, получим то, что на видео. Это и есть та проблема, чтоя решил и поделился. А каждый сам решает, нужно ему это или нет. Халява, она такая.
0
Разумеется, проблему можно и не получить. Если большой предделитель, малая загрузка и т.д. Но это мина под задницей. Которая неизвестно когда рванёт.
Да и вообще, раз используется режим захвата, надо его и использовать, а не изобретать хрень с обнулением.
Вам это известно, мне известно. А кому-то может и нет.
0
> если что-то можно сделать быстро, зачем это делать медленно
вопрос конкретной задачи. так как это ваша задача, то вам и оптимизировать. я лишь рыбу набросал

> всё volatile между прочим
volatile там совершенно не нужен.
вы в цикле зовете функцию mcu_timers_manager, которая является sequence point (с99 standard, sequence point, annex c), т.е. барьером для компилятора, т.е. никаких оптимизацией применяться не будет. а вот код каждого обработчика прерывания _с_ volatile переменной будет создан куда неоптимальней, так как любая операция над volatile переменной обязует компилятор читать/писать в память, что совершенно не нужно. я несколько месяцев назад поднимал топик на эту тему we.easyelectronics.ru/Soft/skolzkaya-dorozhka-dlya-poklonnikov-volatile.html, там это обсуждалось.

> Показания плавают, и плавают не хило. Хотя частота стоит как влитая.
а что из этого следует? не понял

>
0
ат же ж. случайно ушло раньше.

> Та ви шо! А откройте апноту из статьи и посмотрите, как там сделано.
в апноте сказано, что «а мы здесь не учли переполнение. сделайте сами».

> Все примеры, что мне удалось найти, работают через Ж.
я обычно ищу сразу на западных ресурсах, в моем результате:
«google: avrfreaks capture input overflow interrupt»
первая ссылка
шестая ссылка
с шестой ссылки попадаем сразу практически на готовый код

> Это и есть та проблема, чтоя решил и поделился.
ясно. вы слишком много уделили времени на конкретные детали своей задачи, приправив это все соусом «лучшие идеи Светлой Стороны и только для вас». это несколько обескураживает
0
--т.е. барьером для компилятора, т.е. никаких оптимизацией применяться не будет.
Шо шо?.. Сам вызов или тушка функции?

--volatile там совершенно не нужен.
Не нужен. Но вы сравниваете значение регистра, о 16 битах, а он volatile, нравится вам это или нет.
Тут это уже обсуждалось.

--_с_ volatile переменной будет создан куда неоптимальней,
на 4 такта.

--я несколько месяцев назад поднимал топик на эту тему
Дык это прописные истины. Вот только i++ в функции, которая вызывается из прерывания, IAR убьёт. Как минимум выдаст предупреждение. Даже если i глобальная. И волатайл в счётчике как раз на своём месте.

--а что из этого следует? не понял
Что код писали упыри. Там атмега внутри (иногда адук). И явно внутренние таймеры используются для измерения частоты.
На моём коде таких дрожаний нет.

--в апноте сказано, что «а мы здесь не учли переполнение. сделайте сами».
facepalm… Ну причём тут это то?! Смотрите внимательней.

--я обычно ищу сразу на западных ресурсах, в моем результате:
Я ещё и на испанском ищу, и в вашем результате:
1. повышение разрядности счётчика, очевидные вещи 6. «проблема» одновременности прерывания, а «готовый код» работает через такую хитровывернутую Ж, что впору в музей лагов.
Не, конечно я использую подобный метод по случаю. Например тут я измеряю периоды по прерываниям INT и захваченным в них значениями TCNT. На 50 Гц это работает, даже неплохо. Но это НЕ КАПЧА. Это именно захват таймера в стороннем прерывании, я отдаю себе отчёт что это и чем это чревато при перегрузке кода.
А вот использовать капчу и одновременно работать так же, читая TCNT, это уже идиотизм. А приведённые мной ранее ссылки и апнота атмела тому примеры.
Вы мне покажите, от куда ваша «стандартная простыня», что работает правильно ;)

--вы слишком много уделили времени на конкретные детали своей задачи,
отсутствие привнесённой погрешности при использовании капчи, это не моя задача. Это задача вселенского масштаба!
0
Приоритет OVF выше, значит если OVF произойдёт до или одновременно с капчей, обработчик уйдёт туда и выполнит инкремент
Не согласен, приоритет Capture выше, чем OVF.
Сам почти три недели ломал голову над правильным измерением длительноcти/интервала. Читал и этот топик с коментами, но у меня несколько другая задача и решение соответственно другое. Лично убедился в приоритетности этих IRQ :)
0
Упс, меня опередили!
0
TIFR |= 1<<TOV1; // Вы понимаете, что Вы можете наделать своим " |= "?
Подсказка: Этой RMW-операцией Вы могли сбросить другие флаги этого регистра.
TIFR = 1<<TOV1; // Вот правильно.
0
хм. в комментариях подобного рода меня всегда ставит в тупик безапелляционность автора.
подсказка: вы неправы.
0
Гм, почему? Согласно даташиту, флаги в этом регистре сбрасываются записью единицы в соответсвующий бит. Так что инструкция TIFR |= 0xXX сбросит все биты независимо от значения 0xXX.
0
ах да, прощу прощения, это я неправ.
0
t1_ovf должен быть на момент захвата icr1 а не работы прерывания, разве нет? То есть этот код скорее вреден, но даже если его убрать я бы все ещё сомневался, что это будет работать верно. У таймеров бывают интересные особенности на граничных значениях, и это не всегда хорошо расписанов ДШ.
0
я не понял про «t1_ovf должен быть». ovf и icr1 никак не связаны между собой, это два асинхронных события.

ovf может быть выставлен асинхронно (если таймер асинхронный), не важно где мы сейчас находимся.
а вот icr1 либо уже «завернут» (было переполнение), тогда ovf обязательно уже выставлен,
либо icr1 не переполнялся еще, тогда нас и ovf не интересует, отловим в следующий раз.
0
Надо использовать то значение старшей части (t1_ovf) которое было на момент события захвата и которое соответствует значению icr1. А тот код делает проверку на флаг ovf, но оно могло произойти позже события захвата icr1. И ещё события могли произойти в один такт, что тогда будет в icr1 для меня вопрос, top или bottom значение.

Выяснить ответ на последний вопрос и убрать вот этот код,

/* should we handle pended overflow right now? */
if ((TIFR & 1<<TOV1) && !(icr1 & 0x8000)) {
TIFR |= 1<<TOV1;
t1_ovf++;
}

так я вижу корректный вариант, без явных ошибок.
0
> А тот код делает проверку на флаг ovf, но оно могло произойти позже события захвата icr1.

ну представьте: мы копируем перменную. переполнение либо было либо еще нет. через N тактов узнаем, что переполнение было, тогда остается узнать, а затронуло ли случившееся переполнение наше значение, если:
1. значение >= половины, то мы скопировали раньше, чем фактически случилось переполнение, а значит компенсировать (прибавлять t1_ovf на единицу) не нужно.
2. значение < половины, то переполнение было раньше, а значит нужно сейчас отработать переполнение (t1_ovf +=1) и снять прерывание.

все это будет работать, если мы находимся в прерывании не дольше, чем половина оттикавшего таймера.
переполнить 15 бит (половина от TCNT1) — это много (ну с точки зрения нахождения в прерывании), а значит считаем, что обработчик прерывания, работающий дольше чем 32768 тиков таймера — написан неверно и это явно баг.

> И ещё события могли произойти в один такт

ну так ради этого и нужны все эти пляски и подпрыгивания с if ((TIFR & 1<<TOV1) && !(icr1 & 0x8000)),
только для устранения гонки этих двух событий.
0
А теперь ясно, не сразу понял эту логику с половиной.
0
для пущей наглядности:
(исходник диаграммы: goo.gl/S7v59)

0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.