Удержание времени с помощью таймера прерывает встроенный микроконтроллер - PullRequest
4 голосов
/ 21 мая 2009

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

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

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

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

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

sec_counter = 500; // 500 seconds

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

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

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

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

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

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

Есть еще идеи?

Ответы [ 11 ]

2 голосов
/ 21 мая 2009

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

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

1 голос
/ 23 мая 2009

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

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

1 голос
/ 21 мая 2009

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

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

1 голос
/ 21 мая 2009

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

1 голос
/ 21 мая 2009

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

Относительно обсуждаемого вопроса я предлагаю следующее:

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;
}

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

1 голос
/ 21 мая 2009

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

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

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

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

do {
    value = sec_counter;
} while (value != sec_counter);
0 голосов
/ 02 июня 2017

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

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

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

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

do {
       t = timer;
 } while (t != timer);
0 голосов
/ 19 августа 2010

Один из подходов состоит в том, чтобы прерывание сохраняло байтовую переменную и имело что-то еще, что вызывается как минимум один раз каждые 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);
}
0 голосов
/ 21 мая 2009

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

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

0 голосов
/ 21 мая 2009

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

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

...