Системный таймер — SysTick

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

Конкретно в предложенной реализации мы решим только первую проблему, подумайте, что нужно сделать, что бы решить вторую.

Подробнее о таймерах в целом, мы поговорим позже, а сейчас рассмотрим самый простой из них, SysTick, содержащийся в ядре Cortex-M микроконтроллера.

Данный таймер является 24-разрядным (т. е. может принимать значения от 0 до 224-1) и может отсчитывать вниз от заданного значения до нуля, после чего перегружается и генерирует прерывание. Так как таймер содержится в любом Cortex ядре, то его описание вынесено в документацию к ядру — Cortex-M3 Devices Generic User Guide, а все необходимые макросы (и даже функции) хранятся в файле core_<mcu>.h.

systick regs

Таблица со списком регистров системного таймера, Cortex-M3 Devices: Generic User Guide, Table 4-32 System timer registers summary

Регистр контроля и статуса (SYST_CSR)

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

systick csr

Структура регистра CSR

БитНазваниеФункция
[31:17]Зарезервировано
[16]COUNTFLAGВозвращает 1, если таймер после последнего считывания перешел 0
[15:3]Зарезервировано
[2]CLKSOURCEУказать источник тактирования 0 — внешний, 1 — процессор
[1]TICKINTРазрешение прерывания 0 — запретить прерывание по достижении 0, 1 — разрешить прерывание по достижении 0
[0]ENABLEВключение или выключение таймера 0 — отключить таймер, 1 — запустить таймер

Регистр значения перезагрузки (SYST_RVR)

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

systick rvr

Структура регистра RVR

Как видно, из 32 бит используется 24, сюда можно записывать любое число от 0x00000001 до 0x00FFFFFF. Значение должно быть N-1, т.е. если вы хотите отсчитать 100 тактов, нужно указать 99.

Регистр с текущим состоянием (SYST_CVR)

В этом регистре хранится текущее значение таймера.

systick cvr

Структура регистра CVR

Регистр калибровки (SYST_CALIB)

Регистр хранит информацию о калибровке таймера.

systick calib

Структура регистра CALIB

БитНазваниеФункция
[31]NOREFПоказывает, есть ли у устройства эталонная тактовая частота 0 — есть 1 — нет Зависит от производителя! Если устройство не поддерживает эталонный источник, то SYST\_CSR.CLKSOURCE всегда будет 1 и его нельзя изменить
[30]SKEWПоказывает, является ли TENMS точным 0 — точное 1 — неточно или не задано
[29:24]Зарезервировано
[23:0]TENMSОбновляется каждые 10 мс (100 Гц) и сообщает информацию об ошибке. Если значение 0 — то калибровочное значение неизвестно.

Так как таймер — часть ядра, то он входит в библиотеку CMSIS. Откройте файл core_<device>.h и найдите:

Эта структура таймера. Ниже этой секции будут описаны маски для настройки, например:

Итак, чтобы настроить таймер, нужно записать в нужные регистры то, что нас интересует. Допустим, частота тактирования нашего микроконтроллера 24 МГц, тогда:

Теперь нужно заглянуть в таблицу прерываний (startup_<device>.s):

Мы знаем, как должна называться функция. Добавим её в main.c:

В core_<cpu>.h имеется функция для настройки таймера — SysTick_Config:

Поэтому настройку таймера можно переписать следующим образом:

Значение тактовой можно получить из переменной SystemCoreClock (объявлена в файле system_<device>.c). Перепишем строчку выше следующим образом:

Добавьте вызов этой функции в mcu_init().

Теперь усовершенствуем нашу программу мигания светодиодом. Нам потребуется переменная под счетчик. Для чего? В нём будет храниться «глобальное время». Поскольку наш таймер генерирует прерывание каждую 1 мс, мы можем записывать в отдельный счетчик количество миллисекунд, которое прошло с момента подачи питания на МК.

Спецификатор volatile — принуждает компилятор не оптимизировать ваш код, а конкретно заставляет каждый раз считывать значение из адреса где хранится переменная, а не из какой-нибудь временной копии. Нужно это для потому, что значение переменной может быть изменено асинхронно программе, и данные будут просто утеряны. Если ticks_delay не будет обновляться, то программа застрянет в функции delay().

Далее в обработчике прерывания будем добавлять «1» к глобальному времени:

Тогда функцию задержки можно реализовать очень просто (добавим её в utils.c) — запомнив время вызова функции, можно узнать, сколько времени она выполняется: если разница между текущим временем и временем запуска меньше заданного числа, то ждём, в противном случае выходим из цикла.

Теперь цикл в main() можно переписать следующим образом:

Код урока можно найти на github: CMSIS.


Назад | Оглавление | Дальше