Начинающим: счетчик на микроконтроллере с шагом 2/3 микросекунды и переполнением в несколько суток

Начинающим: счетчик на микроконтроллере с шагом 2/3 микросекунды и переполнением в несколько суток

Часто при работе микроконтроллерного устройства есть необходимость отсчитывать «антропоморфное» время – сколько долей секунды светиться светодиоду, максимальный промежуток времени double-click и т. д. В общем, отсчитывать не только нано- и микросекунды, но и десятки миллисекунд, а то и секунды, минуты и даже часы (боюсь сказать о сутках…). В то же время в микроконтроллерах нередко нужно одновременно с этим иметь дело с микросекундами – периоды импульсов, антидребезговое ожидание и т. п. Есть также устройства, которые работают непрерывно многие часы и даже сутки – авиационная техника, автомобильная, скважинные устройства (там речь идет иной раз о непрерывной работе несколько дней). В этих случаях недопустимо переполнение таймеров и 8-битных переменных. Хотелось бы это все объединить в одно изящное и универсальное решение – иметь средство измерения времени с точностью до микросекунды, не переполняющееся несколько дней. Почему бы и нет? Помучился я некоторое время и родил решение для 8-битных микроконтроллеров AVR. Для этого я задействовал 8-битный таймер-счетчик и 4х-байтную переменную. С PIC-ами и AT89 я сейчас не работаю, а с другими embedded-платформами не дружу. Впрочем, если читатели помогут – сделаю и для них. Достоинства – код в высшей степени повторяемый (я уже 5-ое устройство с ним делаю); простота в работе (не используются прерывания для клиентской части работы); клиентская часть кода условно платформенно-независимая; в прерывании – одна операция суммирования (но, правда, для 4-хбайтной величины); нет внешнего устройства — таймера реального времени. Недостаток я нашел один – занят один такой полезный и всегда нужный таймер… Статья будет интересна в первую очередь начинающим — Америку я тут не открыл.

Теория
  1. берем 12 MHz от кварца и берем коэффициент деления на 8 – получаем частоту 1500 KHz;
  2. берем режим CTC (сброс при совпадении) и ставим прерывание на совпадение с 150 – получаем частоту срабатывания прерывания 10 KHz;
  3. на этом самом прерывании инкрементируем переменную (получается инкремент каждые 0.1 миллисекунды);
  4. если это беззнаковая 32х-битная величина, то она переполнится приблизительно после
    • 429496729.6 миллисекунд;
    • 42949.7 секунд;
    • 7158.3 минут;
    • 119.3 часов;
    • 4.97 суток.
  • считаю длительности свечения и пауз между ними светодиодов;
  • учитываю timeouts при работе с UART, USB;
  • задаю всевозможные ситуации в тестовом оборудовании – сложные пространственно-временнЫе комбинации;
  • выдерживаю заданные промежутки времени при опросе АЦП и прочих датчиков;
  • сообщаю компьютеру время своей (устройства) работы и с заданным интервалом времени передаю информацию;
  • с учетом счетчика до микросекунды я осуществляю антидребезговый контроль при нажатии клавиш, анализ импульсов в протяженных линиях.
Настройка для AVR

Как это все сделать в AVR? Прежде всего, заводим внешнюю переменную, которую я называю «ДециМиллиСекунда»:

Как верно подметил @no-smoking, эта переменная должна быть volatile, чтобы ее компилятор не попытался оптимизировать. Инициализацию этой переменной я делаю в функции:

Далее я задаю режим работы таймера 0:

При этом в каком-нибудь MCU_init.h объявляю все, что надо:

Ну и далее, когда можно, разрешаю прерывания:

Осталось описать прерывание. Это проще, чем все предыдущее:

Все, таймер описан, настроен и запущен!

Настройка для PIC

Вот что мне подсказали уважаемые PICоманы:

На пиках это легко повторяется при помощи модуля Timer2. Именно в нем есть аналогичная функция прерывания по совпадению.

PR2 = 75 — значение, при котором таймер обнулится и сгенерирует прерывание T2CON.T2CKPS = 2 — прескалер 1:16 T2CON.T2OUTPS = 0 — без постскалера T2CON.TMR2ON = on — таймер включен

IPR1.TMR2IP = 1 --высокоприоритетное прерывание PIR1.TMR2IF = off --сбрасываем флаг прерывания PIE1.TMR2IE = on --включаем прерывание по совпадению TMR2 и PR2 INTCON.GIE = on --включаем обработку прерываний

Как видно, прескалер тут в 2 раза больше, потому PR2 в 2 раза меньше. Данные настройки будут генерировать прерывания с частотой 10 kHz при системной частоте в 48 MHz (на таймер идет Fosc/4) — стандартная частота для USB Full Speed.

Использование

Код для клиента этого таймера получается кросс-платформенным (если не считать обращения к значению таймера 0 в AVR). Вот фрагмент кода обмена по USB:

Макрофункции RECEIVE_BYTE, RECEIVE_WORD, RECEIVE_DWORD реализуют процедуры чтения с учетом timeout для данной фазы обмена. В итоге, если чего зависло на другой стороне, то микроконтроллер не впадет в «спячку». Обратите внимание – WatchDog не понадобился! И все благодаря переменной/константе max_USB_timeout, которая задает timeout с точностью до 0.1 миллисекунды. Точно также реализуется анализ «тишины в эфире» переменной next_USB_timeout. Это позволяет микроконтроллеру 1) узнать, что компьютер куда-то исчез, 2) как-то об этом сигнализировать (в моем случае загорается светодиод «ошибка»). Константа/переменная MaxSilence_PC_DEV позволяет варьировать понятие «тишины» в широчайших пределах – от доли миллисекунды до нескольких суток. Аналогично реализуются все остальные моменты. Если же вам нужно использовать счетчик микросекунд, то там появляется функция сравнения:

Функции передается предыдущий момент времени – предыдущее значение dmsec и таймера 0. Вначале мы макросом GetUSec останавливаем прерывания, чтобы в момент копирования не испортилось значение dmsec и счетчика. И копируем текущее время. Далее мы приводим разницу во времени к формату 2/3 микросекунды с учетом переполнения. Ну и возвращаем это время. А далее мы это используем в обычном if для контроля антидребезга и прочих мероприятий. Только не забудьте также приостановить прерывания при засекании текущего момента времени – а лучше используйте макрос GetUSec.

📎📎📎📎📎📎📎📎📎📎