Проблема с прескалером таймера Elegoo Nano
Я пытаюсь использовать Timer2 на Elegoo Nano для создания «тикового» прерывания каждые 10 мкс. Мой план состоял в том, чтобы установить Timer2 в режим CTC с прескалером /8 (чтобы получить тактовую частоту таймера 2 МГц от тактовой частоты чипа 16 МГц) и значением сравнения 19 (чтобы сбрасывать таймер каждые 20 отсчетов), что должно запускать ISR CompareA при 100 кГц (каждые 10 мкс). Проблема в том, что только значения предварительного масштабирования 64 или выше работают должным образом. Установка прескалера на любое значение ниже 64 фактически приводит к тому, что «тиковый» интервал становится намного длиннее (в 10 раз или более). Я внимательно изучил таблицу данных ATmega628, чтобы убедиться, правильно ли я настраиваю каждый регистр, но не вижу ничего плохого в том, что делаю. Вот мой код...
void initTimer() {
// ---Отключить прерывания во время инициализации таймера
cli();
// --- Значение сравнения счетчика (прерывание CTC срабатывает каждые 10 мкс)
OCR2A = 19;
// ---Очистить регистр TCCR A (не используется)
TCCR2A = 0;
// ---Включить режим CTC с прескалером деления на 8
TCCR2B = _BV( WGM22 ) | _BV( CS21 );
// ---Включить прерывания CTC от Timer2 для сравнения совпадений A
TIMSK2 |= _BV( OCIE2A );
// --- Инициализируем таймер 2 в ноль (в моей ситуации это не требуется)
TCNT2 = 0;
// ---Снова включаем прерывания
sei();
}
SIGNAL( TIMER2_COMPA_vect ) {
/*
** This should execute every 10uS, but for prescaler values
** below 64 the interval is many times longer than expected.
*/
}
Может ли кто-нибудь указать на мою ошибку?
@PhoenixRevealed, 👍1
2 ответа
Лучший ответ:
Хорошо, я понял. Ошибка была не в моей инициализации таймера, а в том, когда я это делал. Хотя я не показывал это в исходном примере кода, моя функция инициализации на самом деле возвращает фиктивное логическое значение, поэтому я могу вызывать ее автоматически, если в проект включен файл .cpp, содержащий функцию и ISR, как здесь...
bool initTimer() {
// ---Установим таймер для прерываний CTC с периодом 10 мкс
---
---
---
// ---Это возвращаемое значение необходимо, но игнорируется
return true;
}
bool dummy = initTimer();
Эта последняя строка вызывает initTimer() во время загрузки Arduino, просто включив этот исходный файл в проект. Это хороший способ «автоматической настройки» служебных объектов без необходимости явного вызова функции инициализации. Логическое фиктивное возвращаемое значение из initTimer() необходимо, поскольку компиляторы C/C++ блокируют вызов функций таким образом, не сохраняя возвращаемое значение.
void initTimer() {
// ---Делай что-нибудь...
}
// ---Компилятор не может это переварить...
initTimer();
В любом случае, я использовал эту технику много раз на протяжении нескольких десятилетий в настольных и мобильных приложениях, поэтому я сделал это в этом проекте Arduino, не особо задумываясь, но я не учел, что инициализация таймера2 будет выполнена ДО того, как Процедура настройки Arduino. Все, что я делал с timer2, перезаписывалось загрузочным кодом Arduino, который запускался после моей функции initTimer(). Моя инициализация оказала некоторое влияние на работу таймера 2, поскольку настройка загрузки Arduino предполагает, что регистры конфигурации таймера 2 начинаются пустыми (обнуленными), поэтому он просто устанавливает биты конфигурации, не очищая сначала все. Любые биты, которые я включаю, остаются включенными, но при загрузке Arduino устанавливаются дополнительные биты, поэтому результирующая конфигурация таймера непредсказуема. Решение было простым... вместо этого вызовите функцию initTimer() из стандартной функции Arduino setup(), чтобы убедиться, что она выполняется ПОСЛЕДНЕЙ.
Может ли кто-нибудь указать на мою ошибку?
Ваша ошибка состоит в том, что вы думаете, что за период в 10 мкс можно сделать больше, чем вы можете.
При частоте 16 МГц каждая инструкция занимает 63 нс (для однотактовых инструкций). 10nS предоставляет вам максимум (10 000/63) 156 инструкций по сборке. В него вам нужно вписать преамбулу и заключительную часть, чтобы сохранить регистры в стек и получить их снова.
Оставшиеся такты (возможно, не более 100) не дадут вам много времени для выполнения сложных задач. А учитывая, что многие вызовы API Arduino довольно тяжелые, даже что-то столь «простое», как digitalWrite()
, может переполниться столько раз.
- Прерывания таймера Arduino для PID
- Работа двигателя в течение 3 секунд непрерывно с прерыванием и без него
- Использование прерывания внутреннего таймера для чтения аналогового датчика
- Arudino получает команду прерывания ДО перехода в спящий режим, из-за чего он не получает никаких команд прерывания для пробуждения.
- Как указать имя таймера в зависимости от чипа, в который он будет компилироваться?
- Использование millis() и micros() внутри процедуры прерывания
- Arduino непрерывно считывает значение АЦП с помощью прерывания
- Использование TIMER0_COMPB_vect
Спасибо, Маженко, я об этом подумал, но поскольку мой ISR просто уменьшает несколько переменных обратного отсчета, я решил, что, вероятно, мне это сойдет с рук. См. ответ, который я опубликовал на свой собственный вопрос... Я правильно настраивал таймер, но делал это таким образом, чтобы это происходило до запуска кода настройки загрузки Arduino, поэтому мои настройки перезаписывались, когда процедуры Arduino настраивали timer2. . После того, как я изменил свой код, чтобы вместо этого вызвать инициализацию моего таймера2 из setup(), интервал ISR 10 мкс работает нормально., @PhoenixRevealed