- Виртуальное время
Чуть ли не единственное, для чего вот прямо необходимы аппаратные таймеры - переключение задач в ОС. Все остальное при сильном желании можно реализовать ручным подсчетом тактов.
- Определять относительный порядок событий.
Вот такой задачи вообще не припомню.
Георгий Курячий Это просто более высокоуровневая задача. Стоит начать делать свой планировщик, как она непременно появится.
Часто нужно "подождать примерно N тактов (или M секунд, что одно и то же)". Или "выполнить подпрограмму раз в N тактов". Желательно, не блокируя остальные подпрограммы. Тут да, mcycle / SysTick отлично подходят. Даже в Ардуине запилили свой аналог, millis (правда, в AVR выделенного таймера нет, пришлось костылить на обычном).
- секундомеры (с событием по каждому интервалу) и таймеры (с событием по окончанию отсчёта, частный случай)
Вопрос терминологии. Термина "секундомер" я вообще не припомню.
Георгий Курячий Таймеры (в частности, стандартизированные в RISC-V) генерируют ровно одно событие после настройки. Заменил на «таймер-счётчики» (далее будет их упоминание).
Есть **таймеры** (устройства, считающие такты какого-то внутреннего генератора) и есть **счетчики** (считающие внешние события). Причем почти всегда случаев это одно и то же устройство, **таймер - счетчик**. Разница ведь только в том, откуда берется тактовый сигнал.
По мере усложнения:
- - просто счет - генерация прерывания по переполнению. Можно написать обработчик, записывающий в регистр счета нужное значение, что даст генерацию прерывания раз в сколько-то тактов. - ШИМ (управление ножкой ввода-вывода по событиям сравнения с регистром и по переполнению). Используется, скажем, для регулировки напряжений или, например, для управления источниками питания, двигателями, яркостью лампочек... да чем угодно. Зачастую регистр сравнения не один, а 2 - 4. Так, три канала ШИМ вполне хватает для управления трехфазным двигателем. - События по сравнению с регистром. Генерация прерывания, сброс счетчика в ноль, изменение направления счета. - Счет внешних событий. - Взаимодействие со "счетной" периферией. Например, аппаратная обработка энкодеров - устройств определения скорости вращения двигателя. Скажем, для ЧПУ... или для обычной мышки. Не пропустить важное событие в будущем. Для этого нужны будильники. Процессор при этом может быть (частично) обесточен.
Тут согласен. Это важное применение таймеров - счетчиков.
Георгий Курячий Смысл будильника в том, что не (только) процессор обрабатывает его срабатывание, но и сам будлильник умеет будить процессор, и это как бы существенно другая аппаратная функция.
Но все же уточню: для автономной работы (со своим персональным тактовым генератором, полностью независимым от работы ядра) почти всегда выделяют единственный таймер, RTC. Причем помимо собственно таймера зачастую вешают на него функцию часов. Разделение на часы-минуты-секунды, дни-месяцы-годы - да, прямо в 60-ричной системе счисления. И со всеми "прелестями" асинхронного устройства. Необходимость правильного чтения - записи регистров, необходимость специальных ритуалов синхронизации, ...
- Разрешение
fun fact: В ARM (я сейчас говорю про Arm-M3, которые в простых микроконтроллерах используются. Возможно, в более сложных сделали более разумно) есть аналог нашего mcycle - специальный таймер SysTick. Правда, разрешение там всего 24 бита - совсем мало. Ну разве что переключать задачи в ОС годится. А разработчики WCH, когда копировали периферию ARM-контроллеров к себе, посмотрели на этот таймер, посмотрели на mcycle, решили что делать и то и другое бессмысленно, а SysTick удобнее - и реализовали именно его. Но, к счастью, разрядность все же сохранили 64-битную, от RISC-V, а не от ARM.
- watchdog сброса + программная обработка = монотонные часы ∞ объёма)
Вот как раз watchdog в качестве часов работает хуже всего.
Георгий Курячий Снова спор о терминах, слово watchdog убрал.
Его задача ребутнуть ядро, если оно зависнет. Поэтому и источник тактирования у него зачастую свой (а это значит, не капризный кварц, а RC-цепочка, у которой генерация-то не сорвется, но вот частота плыват), и сбрасывают его часто и в непредсказуемые моменты. То есть ядро время от времени говорит "я еще не зависло", и счет "собаки" начинается с начала. Поэтому для счета абсолютного времени watchdog не подходит. Нет, можно, конечно, поизвращаться и использовать его и в этой роли - но точность будет так себе.
- Внешние аппаратные (например, RTC/HPET и т. п.)
Стоит добавить "внутренние аппаратные". Разделение по способу доступа. Пожалуй, исторически так даже правильнее. Внешние аппаратные - отдельные микросхемы со своими специальными протоколами и со своим независимым тактированием. Внутренние аппаратные - часть того же кристалла, что и ядро, доступ через MMIO. Внутренние ядерные - часть ядра, а то и уровня привилегий (отдельно для M-mode, отдельно для U-mode). И программные.
Георгий Курячий Вот тут не понял — чем «Внешние аппаратные» отличаются от «внутренних аппаратных». Тем, что разведены на том же кристалле? А на что это влияет в смысле управления ими? Кажется, есть масса примеров, когда одно и то же по протоколу устройство может быть как на кристалле, так и вне его.
- Если источников для устройств времени несколько, их надо время от времени) согласовывать.
Зачем? Для счета "абсолютного" времени не нужно больше одного аппаратного таймера. Все остальные могут заниматься другими делами. Генерация ШИМ, генерация прерываний для опроса периферии, генерация каких-то внешних сигналов. Счет скорости двигателя (по количеству импульсов с энкодера).
Генерация прерываний для переключения задач. Ни для чего из этого не нужна синхронизация с "абсолютным" временем.
Георгий Курячий Вот тут, например, точно нужна синхронизация — в случае, если цена ошибки за невовремя переключённую задачу высока.
- Кварцевый резонатор
RC-генератор. Максимально простой схемотехнически, максимально надежный, компактный, с почти неограниченным диапазоном частот, даже с возможностью перестройки частоты. Но неточный.
Одна из проблем кварцевого генератора - собственно кварц. Это внешний компонент, который может отломаться, дорожки может разъесть коррозия, может закоротить вода. Сама пластина может треснуть от удара. Поэтому современные МК стартуют от встроенного RC-генератора, и только потом могут переключиться на кварц. А при его сбое автоматически переключаются обратно на RC-генератор (с вызовом соответствующего прерывания).
Впрочем, лично я с такими сбоями не сталкивался. Да и вообще, для "больших ПК" этот аспект вряд ли критичен.
- (mtime) Это MMIO, а не CSR, потому что:
А вот это спорный вопрос. Аналогичные CSR тоже есть. Для stime, htime, utime речь действительно может идти о CSR, чтобы обеспечить виртуализацию.
Георгий Курячий Они не «аналогичные». Они производные от mtime MMIO — собственного кварца/RC-то у них нет. Нужны для того, чтобы с каждого уровня можно было управлять.
Собственно, mtime - штука предельно простая и дешевая - счетчик да компаратор. Даже сумматора нет. Уж по одному на режим привилегий выделить несложно. Тем более что на одном кристалле зачастую встривают несколько куда более сложных периферийных таймеров (с ШИМ, с реверсом, с DMA, выходом на другие таймеры, ...).
- mtime — в действительности не таймер, а часы (RTC) — в тиках; перевод в секунды — ответственность окружения
Зависит от применения. Если mtime используется для переключения задач (а в freeRTOS это по умолчанию именно так), то просто таймер со сбросом по сравнению (а в битах его настройки есть и такой). Но можно вместо сброса реинициализировать mtimecmp, тогда он остается таймером. Считать ли его "часами" - не уверен. С натяжкой, наверное, можно.
Георгий Курячий Согласно определению в начале лекции, mtime — это часы
- Прерывание произойдет, когда время в time* станет больше или равно значению в timecmp*.
ЕМНИП, в RARS - строго равно. То есть если в mtimecmp записать число, меньшее mtime, прерывание не сгенерируется. В реальности, кажется, тоже. Если удастся "проскочить" строгое равенство, флаг прерывания не взведется. Но это, конечно, непросто...
- Замечание от меня: 64-битный режим работы с регистрами снимает подобные вопросы — до тех пор, пока мы не собираемся считать что-то действительно очень быстро растущее)…
Насколько быстро растущее? Допустим, тактовая частота у нас 10 ГГц (10¹⁰ Гц). 64-битный таймер считает до ~10¹⁹. То есть переполнение наступит раз в 10⁹ секунд. 32 года.
Георгий Курячий Это если он прибавляет по 1. Так-то мало ли, что он там считает.
- eqv INTERVAL 500
- li t0 1000 # Увеличим на 1000 (секунда)
*снова недовольные звуки* И зачем было дефайнить INTERVAL, если задержка все равно задается магическими числами?..
Георгий Курячий Да, в этом примере «.eqv INTERVAL 500» лишнее, здесь нужно именно магическое число
- li s2 TIMECMP # MMIO таймера
Можно еще сделать sw zero, TIME, t0 чтобы не ресетить при каждом запуске.
- Если записать ненулевой байт в MMIO-регистр 0xFFFF0013, это устройcтво будет вызывать таймерное прерывание каждые 30 выполненных инструкций RARS
Да собственно почти любой "обычный" таймер. Конечно, число 30 никто не хардкодит, но "прерывание каждые N тактов" - основа любого таймера.
- Как предотвратить повторный вход в обработчик, но при этом не потерять сам факт того, что ещё одно прерывание произошло за время обработки.
Не с той стороны заход. Сама по себе периферия не вызывает прерывания, она только выставляет биты в mip. А дальше внутренности ядра уже AND-ят mip, mpie и другие флаги и вызывают прерывание. Ничто не мешает влезть в соответствующие регистры руками и вызвать "чужое" прерывание. Или наоборот, сказать "это я обрабатывать не хочу, сделаем вид, что его и не было". (впрочем, в 1921вг015, если я правильно помню, с этим накосячили, и далеко не все можно сбросить программно). В том смысле, что в прерываниях нет никакой магии. Просто одни бишут битики в регистры, другие на это реагируют.
Георгий Курячий Не с той стороны заход. Вы в объяснении идёте с конца (уже не первый раз). А мы к этому моменту ещё не придумали, как вообще это должно быть реализовано. И вот, изобретаем mip, который до этого не был нужен.
- Напоминаю, что работа с реальным (и недорогим) железом — и уж точно с прерываниями! — описана в «RISC-V на примере микроконтроллеров GD32VF103 и CH32V303»
Чуть более новая версия. https://habr.com/ru/companies/yadro/articles/873520/
Георгий Курячий Там в нумерации сам чёрт ногу сломит ☹
P.S. Да, стоило бы более явно выделить разновидности таймеров: системный (для счета времени ядра, переключения задач и т.п.), периферийные (счетчики внешних сипульсов, ШИМ, ...), RTC, watchdog. Причем я бы это сделал ближе к концу: вот у нас есть счет и прерывание по сравнению, на которое способен даже дубовый mtime, но поверх можно много чего накрутить. Накручиваем больше регистров сравнения и ножки ввода-вывода, получаем периферийный таймер с ШИМ. Накручиваем персональный тактовый генератор и вывод ресета, получаем watchdog. Накручиваем домен батарейного питания, тактовый генератор и шестидесятичетыричные счетчики, получаем RTC.
