Начинающим: счетчик на микроконтроллере с шагом 2/3 микросекунды и переполнением в несколько суток
Часто при работе микроконтроллерного устройства есть необходимость отсчитывать «антропоморфное» время – сколько долей секунды светиться светодиоду, максимальный промежуток времени double-click и т. д. В общем, отсчитывать не только нано- и микросекунды, но и десятки миллисекунд, а то и секунды, минуты и даже часы (боюсь сказать о сутках…). В то же время в микроконтроллерах нередко нужно одновременно с этим иметь дело с микросекундами – периоды импульсов, антидребезговое ожидание и т. п. Есть также устройства, которые работают непрерывно многие часы и даже сутки – авиационная техника, автомобильная, скважинные устройства (там речь идет иной раз о непрерывной работе несколько дней). В этих случаях недопустимо переполнение таймеров и 8-битных переменных. Хотелось бы это все объединить в одно изящное и универсальное решение – иметь средство измерения времени с точностью до микросекунды, не переполняющееся несколько дней. Почему бы и нет? Помучился я некоторое время и родил решение для 8-битных микроконтроллеров AVR. Для этого я задействовал 8-битный таймер-счетчик и 4х-байтную переменную. С PIC-ами и AT89 я сейчас не работаю, а с другими embedded-платформами не дружу. Впрочем, если читатели помогут – сделаю и для них. Достоинства – код в высшей степени повторяемый (я уже 5-ое устройство с ним делаю); простота в работе (не используются прерывания для клиентской части работы); клиентская часть кода условно платформенно-независимая; в прерывании – одна операция суммирования (но, правда, для 4-хбайтной величины); нет внешнего устройства — таймера реального времени. Недостаток я нашел один – занят один такой полезный и всегда нужный таймер… Статья будет интересна в первую очередь начинающим — Америку я тут не открыл.
Теория- берем 12 MHz от кварца и берем коэффициент деления на 8 – получаем частоту 1500 KHz;
- берем режим CTC (сброс при совпадении) и ставим прерывание на совпадение с 150 – получаем частоту срабатывания прерывания 10 KHz;
- на этом самом прерывании инкрементируем переменную (получается инкремент каждые 0.1 миллисекунды);
- если это беззнаковая 32х-битная величина, то она переполнится приблизительно после
- 429496729.6 миллисекунд;
- 42949.7 секунд;
- 7158.3 минут;
- 119.3 часов;
- 4.97 суток.
- считаю длительности свечения и пауз между ними светодиодов;
- учитываю timeouts при работе с UART, USB;
- задаю всевозможные ситуации в тестовом оборудовании – сложные пространственно-временнЫе комбинации;
- выдерживаю заданные промежутки времени при опросе АЦП и прочих датчиков;
- сообщаю компьютеру время своей (устройства) работы и с заданным интервалом времени передаю информацию;
- с учетом счетчика до микросекунды я осуществляю антидребезговый контроль при нажатии клавиш, анализ импульсов в протяженных линиях.
Как это все сделать в 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.