ARDUINO Прошу подсказки по настройкам таймера Arduino Nano

ILS

✩✩✩✩✩✩✩
22 Авг 2020
3
0
Приветствую! Вопрос про Timer_1 в Arduino Nano на базе процессора ATMega 328P. Программирую в Arduino IDE.
Подскажите, пожалуйста, как правильно с помощью команд на C/C++ включить таймер и считывать с него миллисекунды внутри функции прерывания ISR(PCINTx_vect) (прерывание по пину), при этом чтобы он не переполнялся хотя бы секунд 30.
Стандартные функции языка Arduino и функции библиотеки GyverTimers ориентированы на работу со временем по прерываниям, мне же нужно, чтобы функция ISR(PCINTx_vect) оставалась активной всё время выполнения алгоритма, а в режиме сна отсчёт времени не нужен вообще. Функциями setup() и loop() не пользуюсь.

Предварительная версия программы организована также, но "отсчитывает" время по количеству выполненных циклов. Проблема в том, что в зависимости от условий алгоритм одного цикла состоит из разного количества операторов, что приводит к непредсказуемости времени работы, а время нужно точное. Предварительная версия работает вполне исправно, если закрыть глаза на то, что весь алгоритм может работать 5 секунд вместо 6 и наоборот. Идентичная (с поправкой на другие регистры, тактирующую частоту и предделители таймера) программа для MSP430 также исправно работает.

В функции main() - только отключение WatchDog, инициация пинов и переход в режим сна со следующими параметрами:
C:
PRR |= 0xEF;       //  Отключение периферии для экономии энергии
SMCR |= 0x04;      //  Режим сна Power-down
while(1) {
  SMCR |= 0x01;    //  Разрешение режима сна
  asm("sleep");    //  Уход в режим сна
}
В функции прерывания по пину пытаюсь активировать таймер таким образом:
C:
TCCR1B |= 0x05;   //  Выбор предделителя на 1024
PRR &= ~0x08;     //  Включение Timer_1
Текущее значение таймера в цикле получаю через timer = TCNT1; (timer переменная типа unsigned long, погрешность в пределах времени выполнения одного-двух циклов для моей задачи вполне допустима).

Перед завершением функции прерывания - выключение таймера:
C:
PRR |= 0x08;      //  Выключение Timer_1
TCNT1 = 0x00;     //  Сброс регистра отсчёта Timer_1
Согласно даташиту ATMega 328P, при таких настройках значение переменной timer в цикле должно приблизительно равняться количеству миллисекунд, прошедшему с момента включения таймера минус время, прошедшее с последнего присваивания: тактирующая частота 1Mhz, предделитель таймера на 1024, итого - около 977 прибавлений к регистру TCNT1 в секунду, переполнение чуть больше, чем через минуту.

По всей видимости, в реальности значение отличается, причём критически, т.к программа в лучшем случае работает не то время, которое ожидается, в худшем - вообще не срабатывает.
Программатора нет, и я не умею (в общем-то новичок в теме) без него "подсмотреть" значения переменных, при этом проект одноразовый, поэтому о приобретении или сборке программатора речь не идёт. Возможно, кто-то может подсказать, что не так с настройками? Заранее спасибо.
 
Изменено:

poty

★★★★★★✩
19 Фев 2020
3,228
939
Не уверен, что всё понял правильно, но нужно учитывать иерархию прерываний и возможность использования этого механизма для достижения заданной цели. Из написанного я не очень понял что нужно сделать и почему это нужно делать именно внутри прерывания? Почему не использовать таймеры micros? Они не аппаратные, конечно, но...
Если не хотите писать здесь, напишите в личку.
 

ILS

✩✩✩✩✩✩✩
22 Авг 2020
3
0
Почему не использовать таймеры micros?
Даже не знаю, что это такое :) Иерархия прерываний не подходит - я писал, что не хочу использовать реализации таймера с прерываниями. Причина: вне активации по кнопке - бОльшую часть времени - от ардуино не требуется ничего, кроме сохранения текущих значений переменных и минимального энергопотребления. Но решение уже подсказали: очевидно, ардуинщики при сборке устройства сбросили фьюз CKDIV8, и система работала на 8Mhz. Настройка регистра CLKPR решила проблему (на практике более эффективно для моей задачи оказалось снизить основную тактирующую частоту даже до 0,5 Mhz). Кроме того, при наличии единицы в третьем бите PRR (бит PRTIM1) регистры таймера вообще не реагируют на изменения, поэтому во включении и выключении таймера операторы, приведённые в первом посте, были переставлены местами.
Если вдруг кому-то понадобится:
в начале программы (или в любой момент, когда нужна частота ниже 8Mhz)
C:
CLKPR = 0x80;    // Установка бита, разрешающего изменения делителя частоты, с одновременной очисткой остальных битов
CLKPR = 0x03;    // Установка требуемого делителя частоты с одновременной очисткой разрешающего бита
// 0x02 соответствует частоте 4MHz
// 0x03 - 1MHz
// 0x04 - 0.5MHz
// 0x05 - 0.25MHz
// 0x06 - 125kHz
// 0x07 - 62.5kHz
// 0x08 - 31.25kHz
// Другие значения зарезервированы. Оператор следует делать безусловным (т.е. не "|=", а "=" )
// Если нужно восстановить настройки, достаточно первого оператора,
// через четыре такта разрешающий бит сбрасывается аппаратно
Если, как у меня, надо включать и выключать таймер для экономии:
Включение
C:
PRR &= ~0x08;     //  Включение Timer_1 (подача питания на модуль таймера)
TCCR1B |= 0x05;   //  Выбор предделителя на 1024 (этот оператор также и активирует отсчёт,
                  //  для других настроек таймера см. даташит)
TCNT1 = 0;        //  Сброс регистра отсчёта Timer_1 на ноль. Если не делать сброс,
                  //  таймер продолжит счёт с места, на котором остановился
Выключение
C:
PRR |= 0x08;      //  Выключение Timer_1 (обесточивание модуля таймера.
                  //  Счётчик при этом не сбрасывается, но становится недоступен, как и все остальные регистры таймера)
 
Изменено:

ILS

✩✩✩✩✩✩✩
22 Авг 2020
3
0
Я тут помаленьку разобрался с передачей данных на виртуальный COM-порт для отладки, в процессе обнаружился момент, который кому-то может быть важен:
Если нужно восстановить настройки, достаточно первого оператора
Недостаточно. Хотя согласно datasheet седьмой бит CLKPR сбрасывается аппаратно через четыре такта, практика показала, что без прямой инструкции CLKPR = 0; частота по умолчанию не восстанавливается (у меня не получилось подобрать настройки, чтобы функции Serial работали с частотой 500kHz, поэтому перед передачей данных сбрасываю изменение частоты, а после - возвращаюсь на 500kHz)