Вопрос по прерываниям и таймерам
- Войдите на сайт для отправки комментариев
Сб, 19/02/2022 - 14:24
Добрый день, камрады
"Изобретаю" детектор нуля для управления нагрузкой согласно этого апнота https://www.microchip.com/content/dam/mchp/documents/OTH/ApplicationNotes/ApplicationNotes/Atmel-2508-Zero-Cross-Detector_ApplicationNote_AVR182.pdf. Планировал ловить внешнее прерывание, в обработчике вывод переводить в HIGH и запускать таймер, который по 4 мсек переведет вывод в LOW. Код таков:
#define PIN_IN 2 #define PIN_OUT 4 void setup() { pinMode(PIN_IN, INPUT); pinMode(PIN_OUT, OUTPUT); attachInterrupt(0, action, CHANGE); Timer2_init(); } void action() { cli(); PORTD |= (1 << PD4); TCNT2 = 0; TIMSK2 |= (1 << OCIE2A); sei(); } void Timer2_init() { TCCR2A = 0; TCCR2B = 0; TCCR2A |= (1 << WGM21); TCCR2B = (1 << CS21) | (1 << CS22); OCR2A = 249; } ISR (TIMER2_COMPA_vect) { cli(); TIMSK2 &= ~(1 << OCIE2A); PORTD &= ~(1 << PD4); sei(); } void loop() { }
черт, рано кнопку отправить нажал. В общем на экране осциллографа наблюдается следующая картинка:
То есть четкая сработка на растущем фронте, и какие-то слишком короткие импульсы на падающем фронте.
Если поменять функцию action() так:
(то есть отказываемся от таймера), то картинка приобретает вполне товарный вид:
Вопрос - что я делаю не так, когда пытаюсь отключать выход по таймеру?
Останови таймер как положено. Сбрось прескалер. Сбрось флаги прерываний.
А как его положено останавливать? До Вашего сообщения я был уверен что TIMSK2 &= ~(1 << OCIE2A) делает именно это.
TCCR2B = 0; - так? При кажом запуске таймера его нужно заново выставлять?
TIFR2 &= ~(1 << OCF2A); так? был уверен что вызов обработчика прерывания сбрасывает флаг
Нет по всем пунктам. Читай даташит, всё расписано.
(ну по третьму пункту сказал правильно, но не учел других нюансов)
Нет по всем пунктам. Читай даташит, всё расписано.
(ну по третьму пункту сказал правильно, но не учел других нюансов)
1 и 2 TCCR2B &= ~((1 << CS20) | (1 << CS21) | (1 << CS22));
3 как писал ранее TIFR2 &= ~(1 << OCF2A);
Что еще не учел? (вроде как заработало, но судя по оговорке "но не учел других нюансов" чувствую что это не всё)
GTCCR
Всё это очень занимательно, но если слепой ведет слепого - оба упадут в яму. ;)) Прескеллер? Ну да, ну да.
Подпишусь на тему.
т.е. первый и второй пункты GTCCR |= (1 << PSRASYNC) вместо TCCR2B &= ~((1 << CS20) | (1 << CS21) | (1 << CS22))?
Всё это очень занимательно, но если слепой ведет слепого - оба упадут в яму. ;)) Прескеллер? Ну да, ну да.
Подпишусь на тему.
Умными не рождаются, умными становятся. как раз после падений в яму и выкорабкивания из неё :)
Всё это очень занимательно, но если слепой ведет слепого - оба упадут в яму. ;)) Прескеллер? Ну да, ну да.
Подпишусь на тему.
Умными не рождаются, умными становятся. как раз после падений в яму и выкорабкивания из неё :)
Ну, так-то - да. Но когда слушать коллегу Ркита станет невмоготу - напиши. Я пока за компом. ;)
Читаю в описании регистра TIFR2 - бит OCF2A аппаратно очищается при обработке соответствующего вектора прерывания. Какой смысл в принудительном сбросе флага?
Ну, так-то - да. Но когда слушать коллегу Ркита станет невмоготу - напиши. Я пока за компом. ;)
Так напейсал, дайте пинка в нужном направлении...
Dinosaur, по скетчу тоже есть замечания. Но интересно другое, у вас похоже ложные срабатывания по INT0 идут. Уберите из скетча всё, связанное с таймером. А в Void action вставьте только PORTD ^= (1 << PD4); И осциллограммы в студию.
Читаю в описании регистра TIFR2 - бит OCF2A аппаратно очищается при обработке соответствующего вектора прерывания. Какой смысл в принудительном сбросе флага?
Смысл в том, что флаг поднимается, даже когда обработчик выключен. И как только ты его включаешь, он вызывается. Потому и проскакивала игла. Снятие флагов прерывания (всех) входит в остановку таймера.
Dinosaur, по скетчу тоже есть замечания. Но интересно другое, у вас похоже ложные срабатывания по INT0 идут. Уберите из скетча всё, связанное с таймером. А в Void action вставьте только PORTD ^= (1 << PD4); И осциллограммы в студию.
Смысл в том, что флаг поднимается, даже когда обработчик выключен. И как только ты его включаешь, он вызывается. Потому и проскакивала игла. Снятие флагов прерывания (всех) входит в остановку таймера.
Спасибо за помощь и ликбез. Если еще что то упустил, поошу ткнуть носом.
Ну, так-то - да. Но когда слушать коллегу Ркита станет невмоготу - напиши. Я пока за компом. ;)
Так напейсал, дайте пинка в нужном направлении...
тыкаю: никто никогда не останавливает таймеры. Иликтричество экономишь? ;)) Прерывания запрещай и разрешай. Перед разрешением - сбрось флаг. Точку прерывания установи на свои +4 мс, по тикам. Или OCR не трогай, а сбрасывайв ноль каунтер. Это от личных вкусов зависит. ;)).
А смеялся я над дебильным советом останавливать таймер. Ркит - он хороший, но сильно теоретик.
тыкаю: никто никогда не останавливает таймеры. Иликтричество экономишь? ;)) Прерывания запрещай и разрешай. Перед разрешением - сбрось флаг. Точку прерывания установи на свои +4 мс, по тикам. Или OCR не трогай, а сбрасывайв ноль каунтер. Это от личных вкусов зависит. ;)).
А смеялся я над дебильным советом останавливать таймер. Ркит - он хороший, но сильно теоретик.
Ок, спасибо, завтра попробую ваши рекомендации.
Прескалер не в тему сбросил, он продолжает крутиться, на момент следующего старта отсчета в неизвестном состоянии, по задержке получается разброс в пределах периода прескалера.
Прескалер не в тему сбросил, он продолжает крутиться, на момент следующего старта отсчета в неизвестном состоянии, по задержке получается разброс в пределах периода прескалера.
Так вы же ранее писали «Сбрось прескалер» и «GTCCR», как интерпретировать? Не так сбрасываю? И в даташите написано когда прескалер 0, таймер отключён
Dinosaur, если нет проблем сменить выход PD4 на аппаратную ногу таймера, например PD3, то всё делается вот так просто и изящно:
Принцип работы: В прерывании INT0 таймер2 программируется сразу и на поднимание, и на опускание через 4mS своей аппаратной ноги.
Таймер отключен, когда он отключен от часов. Пока часы тикают, тикает прескалер. И если его сбросить в ноль, он продолжит тикать с нуля.
DIMAX
В строке 10 наверное надо WGM21 ?
И при OCR2B=61 уже имеет смысл сбрасывать prescaler (может быть).
DIMAX
В строке 10 наверное надо WGM21 ?
Ага, не заметил, да. Забыл убрать, он там вообще не нужен (Mode=0). Можно и на поменьше делить, да. Тады так:
Камрады, кажется я Вас начинаю понимать (путаница в терминах в моей голове), прошу поправить мою нить рассуждений если ушел в дебри:
Есть регистр предделителя(он же прескалер), он тикает всегда с частотой микроконтроллера. В регистре TCCR2B битами CS20-CS22 задается с какой частотой относительно предделителя будет увеличиваться значение в регистре TCNT2 (если биты CS20-CS22 сброшены - значение в регистре TCNT2 не увеличивается, хотя предделитель в это время тикает). По совпадению (в моем случае) TCNT2 и OCR2A выставляется флаг прерывания OCF2A в регистре TIFR2 (который сбрасывается при вызове обработчика прерывания, а обработчик прерывания вызывается при установленном бите OCIE2A в регистре TIMSK2). То есть получается картина - TCNT2 дотикал до OCR2A, флаг OCF2A выставлен, но из за сброшенного OCIE2A обработчик прерывания не запускается и ждет установки OCIE2A. И как только я его выставляю (не сбросив предварительно OCF2A), обработчик немедленно выполняется. И получаются странные иголки на выходе вместо импульсов заданной ширины.
Также прочитал что сброс прескалера (регистр GTCCR, биты PSRASY и PSRSYNC) не самая правильная процедура, т.к. оказывает влияние на другие таймеры.
Таким образом при запуске таймера при пересечении нуля: нужно сбросить счетчик TCNT2 = 0, сбросить флаг прерывания TIFR2 |= (1 << OCF2A), разрешить прерывание по совпадению TIMSK2 |= (1 << OCIE2A). А в обработчике прерывания по таймеру просто запретить прерывание по совпадению TIMSK2 &= ~(1 << OCIE2A).
Правда меня смущает замечание камрада rkit (в моей задаче эта ошибка некритична, но для понимания темы хотелось бы разобраться до конца) - "Прескалер не в тему сбросил, он продолжает крутиться, на момент следующего старта отсчета в неизвестном состоянии, по задержке получается разброс в пределах периода прескалера". То есть первый тик в TCNT2 пойдет не точно через 1 / 1024 от частоты МК, а через совершенно непредсказуемый период, зависящий от значения прескалера на момент запуска таймера. Насколько это ужос-ужос, и как с этим бороться? На ум приходит - уменьшать делитель (чтобы ошибка на первом тике была меньше), либо при большом делителе - сбрасывать прескалер перед запуском таймера.
Напоследок выкладываю что у меня получлось по мотивам "озарения":
и картинку с осциллографа (с симулятора, железный поленился подключать):
Dinosaur, если нет проблем сменить выход PD4 на аппаратную ногу таймера, например PD3, то всё делается вот так просто и изящно:
Принцип работы: В прерывании INT0 таймер2 программируется сразу и на поднимание, и на опускание через 4mS своей аппаратной ноги.
Спасибо за наводку, как раз смотрел на эти биты и думал "чего я дурак печатку развел не под те ноги"... В будущем учту этот момент. Но вопрос - почему ногу поднимаем так сложно (строки 4-5) всесто записи единички в порт? Нам же сразу ее поднять нужно...
cli() в прерываниях бесполезен (прерывания и так запрещены), а sei() не нужен (т.к. они разрешатся автоматически при выходе), и теоретически может быть вреден.
Dinosaur, потому, что com-биты отключают ноги от gpio.
Если вместо строк 4-5, поднимем пин записью в порт единички, то после шестой строки будет с пином творится хз что?
Dinosaur, то следующей строкой пин отключится и будет low (предполагаю).
Ок, дошло. Спасибо.
тыкаю: никто никогда не останавливает таймеры. Иликтричество экономишь? ;)) Прерывания запрещай и разрешай. Перед разрешением - сбрось флаг. Точку прерывания установи на свои +4 мс, по тикам. Или OCR не трогай, а сбрасывайв ноль каунтер. Это от личных вкусов зависит. ;)).
Спасибо, поизучал вопрос, передалал по Вашим наставлениям. Работает.
Камрады, напоследок под шквал критики представлю функцию регулировки мощности нагревателя (пропуском периодов). Вызывается по прерыванию INT0 (при пересечении нуля). Критика по всем моментам приветствуется.
Напоследок выкладываю что у меня получлось по мотивам "озарения":
ОК. Все правильно, кроме cli() и sei() в ISR. "Масло - масляное" получается. Тебе уже Upper про это написал.
Если в момент выполнения кода по прерыванию произойдёт другое (например) прерывание, то как это будет обработано? Прерывание (второе) будет пропущено, помещено в какую-то очередь или как?
Спасибо за комментарий, почитал про этот момент, действительно лишнее.
Спасибо, поправил.
Вы INT0 имеете ввиду? Да, убегать в новое прерывание по выходу из текущего было бы нехорошо... В моей ситуации не так часто события сыпятся чтобы это было возможно, но я так понимаю неплохо бы проверить перед выходом из прерывания в регистре EIFR флаг INTF0 и при необходимости сбросить его? Верно думаю?
Я в общем спрашивал, к примеру - Вхожим в обработку прерывания по внешнему прерыванию, а в это же время происходит прерывание по таймеру и тоже должно быть обработано. То есть мы уже вошли в обработку первого и ещё не вышли, происходит второе. Как себя мк поведёт?
Зависит от МК. Для 328го по выходу из прерывания, если были другие прерывания, то вызовуться по очереди, согласно преоритету. Если внутри прерывания разрешить прерывания, то прерывание прервётся и вызовется другое. В stm32 завит от приоритета прерываний. Если у прерывания более высокий приоритет, то текущее прерывание прервётся. Но это в общих чертах. Всё сложнее. Надо читать святцы для конкретного случая.
... но я так понимаю неплохо бы проверить перед выходом из прерывания в регистре EIFR флаг INTF0 и при необходимости сбросить его? Верно думаю?
По вашему алгоритму с битовой маской, если по INT0 нет "помех", то как вы и написали, ничего дополнительно делать не надо. Если помехи возможны, то может лучше проверять в обработчике INT0 уровень сигнала на PIN_OUT, и если он высокий - значит это "срабатывание в неожиданный момент" и что делать в этом случае - решать вам. По крайней мере на этапе отладки можно подсчитывать число таких случаев и выводить в Serial.
П.С. (Оптимальность самого алгоритма с битовой маской обсуждать не могу - нет практического опыта управления нагревателями).
Насколько я понял из даташита, 328 запоминает событие, и после выхода из обработчика идем в другое прерывание (согласно приоритету, если их несколько в очереди). Но я прямо не иксперд, надеюсь камрады поправят если соврал.
С ложной сработкой как ни странно проблем нет, пробовал на реальном устройстве гонять. Хотя в апноте 182 тоже пишут мол проверьте сотояние пина 5 раз чтобы убедиться в том что сработка не ложная, но это хорошо когда прерывание по одному из фронтов, а когда по каждому и реакция нужна сразу, решил не усложнять в общем. Я думал имеется ввиду когда мы находимся в обработчике INT0, и за это время происходит еще одно прерывание, обработчик которого будет запущен сразу после выхода из обработчика. Как с этим бороться? (как я выше писал - проверить перед выходом из прерывания в регистре EIFR флаг INTF0 и при необходимости сбросить его) или другие методы?
Dinosaur, я же разобрал принцип на примере, и вроде вполне человеческим языком. Ещё раз: TIFR2 = 1 << OCF2A - это не одна, а две операции МК (см. пример). Первая получает во временном регистре 111, вторая записывая результат в TIFR вызовет его обнуление. Проще уже некуда..
Да, спасибо, дошло. Просто в даташите ничего не увидел про две операции, думал что в один бит записал единичку, и конец мучениям. А тут оказалось что весь регистр переписать надо, а дальше еще и магия происходит )))
Dinosaur, всё таки я несколько поспешил про TIFR2|=, всё сказанное верно, но есть одна особенность -регистр TIFR попадает в диапазон адресов, для которых работает ассемблерная команды sbi, которая умеет записать бит в регистр не читая его предварительно. И компилятор сам догадывается вместо традиционной обработки TIFR|= (прочесть, изменить, и записать обратно) дать короткую команду sbi, соответссно другие биты не сбросятся. Я сам про проблему TIFR|= вычитал когда-то на easielectronics, и просто отложил в голове не проверяя. А тут вот вспомнил, проверил, и оба-на, а компилятор сам догадался и исправил. Но всё равно, нужно отложить в голове, что правильно сбрасывать флаги без "OR".
Но только в некоторых (особенных) регистрах, верно?
P.S. Это нелзя панять, но нужна запомнить - слава "вилька" и "тарелька" пишутся без мягкого знака, а слова "кон" и "сол" пишутся с мягким знаком )))
Хм, а почему так? В даташите вроде как написано Alternatively, OCF2A is cleared by writing a logic one to the flag.