Учет времени с помощью таймера прерывает работу встроенного микроконтроллера

StackOverflow https://stackoverflow.com/questions/892661

Вопрос

Этот вопрос касается программирования небольших микроконтроллеров без операционной системы.В частности, в данный момент меня интересуют фотографии, но вопрос носит общий характер.

Я несколько раз видел следующую схему учета времени:

Код прерывания таймера (допустим, таймер срабатывает каждую секунду):

...
if (sec_counter > 0)
  sec_counter--;
...

Основной код (без прерывания):

sec_counter = 500; // 500 seconds

while (sec_counter)
{
  // .. do stuff
}

Основной код может повторяться, устанавливать счетчик на различные значения (не только секунды) и так далее.

Мне кажется, здесь есть условие гонки, когда назначение на sec_counter в основной строке код не является атомарным.Например, на РИС. 18 присвоение преобразуется в 4 оператора ASM (загружая каждый байт одновременно и выбирая правильный байт из банка памяти перед этим).Если код прерывания появится в середине этого процесса, конечное значение может быть повреждено.

Любопытно, что если присвоенное значение меньше 256, то присвоение является атомный, так что проблем нет.

Прав ли я насчет этой проблемы?Какие шаблоны вы используете для правильной реализации такого поведения?Я вижу несколько вариантов:

  • Отключать прерывания перед каждым назначением sec_counter и включать после - это некрасиво
  • Используйте не прерывание, а отдельный таймер, который запускается и затем опрашивается.Это чисто, но расходуется целый таймер (в предыдущем случае таймер срабатывания на 1 секунду можно использовать и для других целей).

Есть еще какие-нибудь идеи?

Это было полезно?

Решение

Архитектура PIC настолько атомарна, насколько это возможно.Это гарантирует, что все операции чтения-изменения-записи в файл памяти являются "атомарными".Хотя для выполнения всего процесса чтения-изменения-записи требуется 4 такта, все 4 такта используются в одной инструкции, а следующая инструкция использует следующий 4-тактовый цикл.Именно так работает конвейер.В режиме 8 часов две инструкции находятся в стадии разработки.

Если значение больше 8 бит, это становится проблемой, поскольку PIC является 8-разрядной машиной, и большие операнды обрабатываются в нескольких командах.Это приведет к возникновению атомарных проблем.

Другие советы

Запишите значение, затем проверьте, что это именно то значение, которое требуется, казалось бы, самой простой альтернативой.

do {
 sec_counter = value;
} while (sec_counter != value);

Кстати, вы должны сделать переменную volatile , если используете C.

Если вам нужно прочитать значение, то вы можете прочитать его дважды.

do {
    value = sec_counter;
} while (value != sec_counter);

Вам определенно нужно отключить прерывание перед установкой счетчика.Каким бы уродливым это ни было, это необходимо.Рекомендуется ВСЕГДА отключать прерывание перед настройкой аппаратных регистров или программных переменных, влияющих на метод ISR.Если вы пишете на C, вам следует рассматривать все операции как неатомные.Если вы обнаружите, что вам приходится просматривать сгенерированную сборку слишком много раз, то, возможно, будет лучше отказаться от C и program in assembly.По моему опыту, это редко бывает так.

Что касается обсуждаемого вопроса, вот что я предлагаю:

ISR:
if (countDownFlag)
{
   sec_counter--;
}

и установка счетчика:

// make sure the countdown isn't running
sec_counter = 500;
countDownFlag = true;

...
// Countdown finished
countDownFlag = false;

Вам нужна дополнительная переменная, и лучше обернуть все в функцию:

void startCountDown(int startValue)
{
    sec_counter = 500;
    countDownFlag = true;
}

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

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

Если вы скачаете бесплатный стек TCP / IP от Microchip, там есть подпрограммы, которые используют переполнение таймера для отслеживания прошедшего времени.В частности, "tick.c" и "tick.h".Просто скопируйте эти файлы в свой проект.

Внутри этих файлов вы можете увидеть, как они это делают.

Не так уж любопытно, что менее 256 ходов являются атомарными - перемещение 8-битного значения - это один код операции, так что это настолько атомарно, насколько вы получаете.

Лучшее решение на таком микроконтроллере, как PIC, - отключить прерывания перед изменением значения таймера.Вы даже можете проверить значение флага прерывания при изменении переменной в основном цикле и обработать его, если хотите.Сделайте это функцией, которая изменяет значение переменной, и вы могли бы даже вызвать ее из ISR.

Итак, как выглядит ассемблерный код сравнения?

Принимая во внимание, что он ведет обратный отсчет и что это просто нулевое сравнение, должно быть безопасно, если сначала проверяется MSB, затем LSB.Может быть повреждение, но на самом деле не имеет значения, находится ли оно посередине между 0x100 и 0xff, а поврежденное значение сравнения равно 0x1ff.

При том способе, которым вы используете свой таймер сейчас, он все равно не будет отсчитывать целые секунды, потому что вы можете изменить его в середине цикла.Так что, если тебя это не волнует.Лучшим способом, на мой взгляд, было бы прочитать значение, а затем просто сравнить разницу.Это занимает на пару операций больше, но не имеет проблем с многопоточностью.(Поскольку таймер имеет приоритет)

Если вы более строго относитесь к значению времени, я бы автоматически отключил таймер, как только он отсчитает до 0, очистил внутренний счетчик таймера и активировал, как только вам это понадобится.

Переместите часть кода, которая будет находиться в main(), в соответствующую функцию и вызовите ее ISR условно.

Кроме того, чтобы избежать каких-либо задержек или пропущенных тактов, выберите этот параметр ISR таймера в качестве прерывания высокого приоритета (PIC18 имеет два уровня).

Один из подходов заключается в том, чтобы прерывание сохраняло байтовую переменную и имело что-то еще, что вызывается по крайней мере один раз каждые 256 срабатываний счетчика;сделайте что-то вроде:

// ub==unsigned char; ui==unsigned int; ul==unsigned long
ub now_ctr; // This one is hit by the interrupt
ub prev_ctr;
ul big_ctr;

void poll_counter(void)
{
  ub delta_ctr;

  delta_ctr = (ub)(now_ctr-prev_ctr);
  big_ctr += delta_ctr;
  prev_ctr += delta_ctr;
}

Небольшое изменение, если вы не возражаете заставить счетчик прерываний оставаться синхронизированным с LSB вашего большого счетчика:

ul big_ctr;
void poll_counter(void)
{
  big_ctr += (ub)(now_ctr - big_ctr);
}

Никто не рассматривал вопрос о чтение многобайтовые аппаратные регистры (например, таймер.Таймер может перевернуться и увеличить свой второй байт, пока вы его читаете.

Скажите, что это 0x0001ffff, и вы это прочитаете.Вы можете получить 0x0010ffff или 0x00010000.

16 - разрядный периферийный регистр представляет собой изменчивый к вашему коду.

Для любого изменчивый "переменные", я использую технику двойного чтения.

do {
       t = timer;
 } while (t != timer);
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top