0

Задержка на таймере avr

Содержание

Увидел в статье DI HALT "Простой программный таймер для конечных автоматов" некий кусок кода и решил накатать статью…

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

Задача 1: выполнить прерывание с заданной задержкой в тиках

Прибавили к текущему счёту нужную задержку и включили прерывание. Важно сначала обновить OCR, а затем включать прерывание, иначе можно включить, а оно тут же сработает — ведь что сейчас в TCNT и OCR мы не знаем. Фактически интервал будет отсчитан от момента чтения TCNT.

Что делать НЕ надо:

  • Останавливать таймер и обнулять TCNT0 — это лишь увеличит код на 2 строчки и не даст использовать таймер параллельно для других целей.
  • 16-битный таймер останавливать тоже не нужно, теневые регистры в таймере как раз для того, чтобы можно было писать и читать на ходу.
  • Заботиться о «переполнении» — оно на самом деле вообще не переполнение, а красивый механизм, позволяющий не хранить лишние данные и не считать лишние разряды. Главное — чтобы интервал не превышал разрядность таймера (255 тиков для 8-битного таймера, 65535 тиков для 16-битного).
Задача 2: выполнять прерывание с заданным периодом

Запустили таймер, включили прерывание, в начале или конце прерывания обновляем OCR.

Что делать НЕ надо:

  • Вместо «OCR0 += 100» писать наподобие «OCR0 = TCNT0 + 100». Так вместо периода ровно в 100 тиков получим 100 тиков + задержка входа в прерывание + пока выполнение дойдёт до обновления OCR.
  • Использовать CTC режим. Он похоронит второй канал и другие ресурсы таймера.
  • Обнулять TCNT — по тем же причинам.
  • Заботиться о «переполнении» (см. задачу 1)
Задача 3: выполнять прерывание с заданным дробным периодом

При тактовой частоте 16 МГц нужно вызывать прерывание с частотой 4800 Гц. Таймер 0 работает с прескалером 64. Получается, что нужен период в 52 и 1/12 тика.

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

Задача 4: узнать сколько прошло времени между метками t1 и t2

Данные меньше разрядности int (8 бит)

Данные больше или равны разрядности int (16, 32, 64 бита)

Достаточно просто вычесть. Главное, чтобы время прошедшее между t1 и t2 не превышало разрядность данных (от 0 до 255 тиков для uint8_t, от 0 до 65535 тиков для uint16_t).

Что делать НЕ надо:

  • Заботиться о «переполнении»: имеющиеся 8 или 16 бит есть по сути младшие разряды полных меток времени. Для того, чтобы вычислить младшие разряды разности достаточно знать младшие разряды уменьшаемого и вычитаемого. Посчитайте в столбик и увидите. Как я уже сказал, «переполнение» (а вернее, обрезание старших разрядов) — это всего лишь механизм, позволяющий не хранить лишние данные.
Задача 5: узнать сколько тиков осталось до метки или прошло после метки

По сути, основа любимых в народе программных таймеров.

Операция "(int8_t)(TCNT0 — t1)" вернёт количество тиков, прошедших с момента времени t1 до текущего. Похоже на предыдущее, но теперь результат был интерпретирован как int8_t. Если t1 ещё в будущем, то результат отрицательный, если уже в прошлом — положительный. Главное, чтобы этот результат вписывался в диапазон int8_t — от -128 до 127. Следовательно, максимальная задержка — 128 тиков для 8 бит, 32768 тиков для 16 бит… Остальная часть диапазона — запас, в течении которго нужно успеть обработать срабатывание.

Что делать НЕ надо:

  • Заботиться о «переполнении» — см. предыдущие задачи.
  • Лепить уродцев типа того, что я привёл в начале поста.
Задача 6: увеличить диапазон задержек программного таймера

Похоже на предыдущее, но теперь разность TCNT0 — t1 интерпретируется как число в диапазоне от -240 до 15. Следовательно, теперь максимальная задержка — 240 тиков и 16 тиков остаётся чтобы успеть обработать срабатывание. Добавление 240 смещает диапазон -240..15 в 0..255 для корректного сравнения. Для данных большей разрядности — заменить 240 на нужный диапазон и убрать приведение к uint8_t.

0xFF00 (65280) тиков из 65536 — максимальная задержка и оставшиеся 256 тиков — запас.

Что делать НЕ надо:

  • Заботиться о «переполнении» (если вы всё ещё не поняли, что «переполнение», а вернее — циклический код — это круто!)
Задача 7: избавиться от ручного пересчёта стандартных единиц времени в тики таймера

Теперь вместо «OCR0 += 100» можно писать «OCR0 += T0_US(400)». Даже при пересчёте 1:1 запись с единицей измерения («T_US(25)») выглядит лучше чем просто число («25»). Особенно в конфиге, где не ясно из контекста 25 чего имеется в виду. Половинка добавляется для более правильного округления.

Что делать НЕ надо:

  • Использовать эти макросы не для констант. Пересчёт в рантайме через флоаты будет убийственно неоптимален, особенно если компилить в avr-gcc без -lm.
Задача 8: расширить разрядность таймера для организации системного тика

Функция t0_update() обновляет значение полного счётчика тиков. Обычно задержки проверяются один раз за проход главного цикла, потому и обновлять значение системного тика удобно один раз из главного цикла. Младшая часть значения получается из аппаратного счётчика, старшая — из посчитанного количества переполнений.

Нужно предусмотреть обработку ситуации когда мы прочитали старший байт (счётчик переполнений), после чего младший (регистр таймера) обнулился и его прочитали уже после обнуления. Тогда, например, если тик должен был смениться с 0x33FF на 0x3400, можно получить некорректное значение 0x3300 — тик резко скакнёт назад. Или может получиться 0x34FF, если читать сначала младший байт. Поэтому читаем сначала старший байт, затем младший и проверяем не изменился ли старший. Если переполнение произойдёт после чтения ovf_ctr в ctr_high мы это обнаружим по изменившемуся ovf_ctr и нужно будет повторить чтение. Желательно ещё проверять флажок переполнения TOV0 на случай если мы прочитали ovf_ctr, TCNT0 переполнился, но прерывание ещё не вызвалось и не успело обновить ovf_ctr, чтобы это можно было обнаружить. Поскольку AVR при возврате из прерывания всегда выполняет ещё одну командочку прежде чем обрабатывать следуюшее, в принципе такое возможно…

Если счётчик переполнений — больше одного байта, для проверки его изменения достаточно проверить только младший байт — он гарантировано меняется при инкрементировании:

Читайте также:  В чем заключается программирование

Что делать НЕ надо:

  • Собирать счётчик тиков из половинок каждый раз, когда нужно к нему обратиться. Достаточно это делать один раз за проход главного цикла.
  • Лепить функцию, которая тупо возвращает текущее значение системного тика. Доступ через глобальную переменную куда как эффективней.
  • Совать в атомики доступ к переменным, которые могут измениться в прерывании. Лучше применять неблокирующие подходы — например, прочитать дважды и сравнить.

Это мой любимый подход в организации системного тика. При тактовой частоте 16 МГц, прескалере таймера 256 получается разрешение тика в 16 мкс и полный цикл в 1 секунду. При прескалере 1024 — 64 мкс и 4 секунды. При этом аппаратный таймер может выполнять ещё кучу полезной работы.

Задача 9: защитить счётчик тиков от случайного изменения

Переименовать фактический счётчик тиков во что-нибудь вроде "__t0_ctr_internal" и написать макрос, обращающийся к нему, например, как #define t0_ctr() (__t0_ctr_internal + 0). Компилятор не позволит присвоить выражению.

Что делать НЕ надо:

  • Страдать этой хренотенью. Мало ли куда и что можно по ошибке записать. Давайте тогда и TCNT в функцию get_tcnt() оборачивать, равно как и все остальные регистры и данные. А, можно и в обход функции к регистру обратиться? Блин. Ну давайте ещё что-нибудь налепим… Все эти ограничительства попахивают роскомнадзорщиной — банить что первое под руку подвернётся.
Задача 10: обойтись без прерывания при подсчёте переполнений

Иногда от лишних прерываний стоит избавляться дабы уменьшить джиттер действительно важных прерываний и уменьшить расход тактов впустую на вход в прерывание и выход. Можно проверять переполнение при обновлении счётчика тиков. Если, конечно, есть уверенность в том, что таймер не успеет переполниться более одного раза за то время, пока тупит главный цикл. Максимальное время оборота главного цикла обычно ограниченно, в т.ч. и сторожевым псом, так что подход вполне применим. Особенно с длинным 16-битным таймером.

Нельзя проверить TOV0 и, если он сброшен, считать что переполнения не было. Переполнение может произойти сразу же после проверки. Зато если флажок сброшен когда уже прочитали — значит всё в порядке. Если же флажок установлен — переполнение было и нужно увеличить старший байтик, а также перечитать TCNT0 — ведь неизвестно было ли переполнение в момент первого чтения. Ну и сбросить флажок не забыть.

Что делать НЕ надо:

  • Использовать этот подход если нет уверенности в том, что время оборота главного цикла всегда меньше полного цикла таймера и недопустимо растягивание задержек при затупах главного цикла
Задача 11: организовать вторичные счётчики времени в миллисекундах и секундах

Имея счётчик тиков высокого разрешения легко можно сделать вторичные счётчики более низкого

Полный цикл t_ms — чуть больше минуты, t_sec — чуть больше 18 часов, t_sec32 — чуть больше 136 лет.

Задача 12: сэкономить память на хранении меток времени

Метки времени могут быть и меньшей разрядности чем счётчик если их хватает для отмеряемых интервалов. В данном случае, t_ms — 16 бит, t_led_off — 8 бит. Главное — не забывать приводить разность к нужному размеру если он меньше чем разрядность уменьшаемого, вычитаемого или int.

При проверке пора ли гасить диодик сперва проверяется включен ли диодик. Хотя делать это и не обязательно, это экономит чутка тактов — проверка бита в порту выполняется быстрее плюс LED_OFF() обходится.

Задача 13: считать наработку оборудования

Будет посчитано суммарное время работы мотора в секундах. Можно использовать для last_update байтик, пока интервал между вызовами функции в него влезает (255 секунд, ещё бы ему не влезть).

Сделаем счётчик моточасов энергонезависимым:

p.s. Давно хотел написать ещё про кольцевые буферы — там тоже рожают тех ещё уродцев. И про работу с TWI по-человечески… А интересно ли это кому-нибудь? :-[

Пример 1. Задержки и опрос кнопочек

Функция опрашивает 4 кнопочки A, B, C, D. 4 младших бита релуьтата указывают на событие нажатия соответствующей кнопочки. Если функция вернула единичку — можно выполнить соответствующее действие. В старшем байте 4 бита указывают на текущее состояние кнопочек, которое полезно, например, если какие-то кнопочки используются в качестве модификаторов («нажми A, удерживая B»). Кнопочки A и C имеют функцию автоматичского повторения — если кнопочку удерживать более 0.5с, функция начинает возвращать событие «нажата кнопочка A» с периодом в 75 мс. Это удобно для кнопочек типа +/-, вверх/вниз и т.п.

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

Простейший способ защититься от дребезга — ограничить интервал опроса кнопочки, чтобы за время нестабильности не опросить кнопочку более 1 раза. В таком случае, неважно, что вернёт нам этот опрос — программа так или иначе увидит один фронт.

Время дребезга контактов для подавляющего большинства кнопочек составляет не более 5-9 мс. Интервал опроса можно выбрать в районе 20-60 мс. Не стоит лепить интервал в сотни миллисекунд — это не даст никакой пользы, лишь будет раздражать юзера медленным откликом устройства.

При опросе мы сперва читаем состояние кнопочек в переменную new_key_state

С состоянием кнопочек в виде флажков, упакованных в переменную очень удобно работать — например, выделить нажатия:

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

Затем можно записать текущее состояние кнопочек

Чтобы сделать повторение при удержании кнопочек заводится переменная «время следующего повторения» для каждой из кнопочек. При нажатии кнопочки записывается время первого повторения через 500 мс.

Затем, если кнопочка всё ещё нажата, проверятеся истекло ли это время. Если да — то повторяется нажатие кнопочки установкой флажка в переменной key_push и записывается время следующего повторения через 75 мс.

Обратите внимание на обновлении метки времени как «t_rept_a += T_KEY_REPT_PERIOD». При такой конструкции будет сегенерировано ровно по одному событию на 75 мс. Даже если главный цикл затупит, скажем, на 750 мс — при следующих проходах будет сгенерировано 10 «нажатий» подряд. Можно переписать как «t_rept_a = t_ms + T_KEY_REPT_PERIOD» — тогда между «нажатиями» будет не менее 75 мс. Что лучше — зависит от задачи…

Функцию read_keys можно вызываеть из главного цикла, например, как-то так

Что делать НЕ надо:

  • Городить развесистые автоматы, когда можно обойтись одним флажком.

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

Одно из решений состоит в использовании встроенного в кристалл микроконтроллера AVR аппаратного таймера. Поскольку максимальная разрядность таймера обычно не превышает 16 бит (иногда 24 бита), то для получения длинных задержек выбирается минимальное значение для частоты тактирования. Например, 24-разрядный таймер, работающий на частоте 1 МГц, может дать задержку только несколько десятков секунд. Для многих приложений этого может оказаться недостаточным.

Другое решение – подсчет количества переполнений таймера. Однако это усложняет программное обеспечение, и не дает ядру оставаться в режиме низкого энергопотребления, когда происходит ожидание истечения задержки.

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

Читайте также:  Дисплей le eco le 2

Здесь представлено решения для генерации и обработки длинных задержек на микроконтроллерах серии AVR AT90 (AT90S2313, AT90S4414 и AT90S8515). Используются встроенные таймеры без участия программного обеспечения, что позволяет ядру находиться в режиме пониженного энергопотребления во время генерации задержки. Поскольку таймеры тактируются от системной частоты, то не требуется каких-то внешних дополнительных компонентов. Для увеличения генерируемого времени задержки используется каскадное включение таймеров AVR.

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

Вот список примерных приложений, где могут понадобиться длинные задержки:

• Таймауты в интерфейсе между человеком и машиной
• Измерения в окружающей среде (уровень звука, загрязнение)
• Регулировка и управление процессом

Прим. переводчика: на самом деле предложенное решение имеет довольно ограниченное применение – оно хорошо подходит только для тех случаев, когда от системы требуется пониженное энергопотребление, и когда используемые таймеры/счетчики негде не должны быть задействованы. Для тех случаев, когда микроконтроллер не входит режим сна, предложенное решение бесполезно, потому что задержки любой длительности элементарно реализуются с помощью обычных счетчиков.

[Таймеры/счетчики микроконтроллеров AVR]

Далее будут коротко рассмотрены таймеры, используемые в AVR. Для дополнительной информации пожалуйста обращайтесь к даташиту "AVR Enhanced RISC Microcontroller Data Book".

Таймеры/счетчики. Серия AT90 предоставляет 2 таймера/счетчика (T/C) общего назначения, один 8-разрядный T/C, и один 16-разрядный T/C. Таймеры/счетчики имеют индивидуальный выбор поделенной тактовой частоты от одного и того же 10-битного предделителя (прескалера). Оба таймера/счетчика могут либо тактироваться от внутренней базовой тактовой частоты, или работать ка счетчик импульсов на внешнем выводе.

Прескалер для T/C. На рис. 1 показана схема основного прескалера для T/C. Есть 4 различных варианта выбора поделенной счетчиком частоты: CK/8, CK/64, CK/256 и CK/1024, где CK тактовая частота генератора. Для двух таймеров/счетчиков еще добавлены к вариантам выбор CK, внешнего тактового сигнала и остановка таймера.

Рис. 1. Прескалер для таймеров/счетчиков.

[8-Bit Timer/Counter0]

На рис. 2 показана блок-диаграмма Timer/Counter0.

Рис. 2. Блок-схема T/C0.

Для 8-разрядного Timer/Counter0 можно выбрать тактирование от CK, поделенной (через прескалер) CK, или от внешнего входа. Дополнительно таймер может быть остановлен, как указано в описании регистра управления Timer/Counter0 Control Register (TCCR0) [2]. Флаги состояния переполнения находятся в регистре Timer/Counter Interrupt Flag Register (TIFR). Управляющие флаги находятся в регистре управления Timer/Counter0 Control Register (TCCR0). Настройки для разрешения/запрета прерывания для Timer/Counter0 находятся в регистре Timer/Counter Interrupt Mask Register (TIMSK).

Когда Timer/Counter0 тактируется от внешнего входа, внешний сигнал тактов синхронизируется с частотой тактового генератора ядра (системными тактами) CK. Чтобы гарантировать корректную выборку внешних тактов, минимальное время между двумя переходами уровня внешнего сигнала тактов должно быть как минимум одному интервалу периода CK. Внешний тактовый сигнал таймера анализируется по нарастанию уровня внутренней системной частоты CK.

[16-Bit Timer/Counter1]

На рис. 3 показана блок-диаграмма Timer/Counter1.

Рис. 3. Блок-схема T/C1.

Для 16-bit Timer/Counter1 можно выбрать тактирование от CK, поделенной (через прескалер) CK, или от внешнего входа. Дополнительно таймер может быть остановлен, как указано в описании регистров управления Timer/Counter1 Control Registers (TCCR1A и TCCR1B). Различные флаги состояния (overflow, compare match и capture event) и управляющие сигналы находятся в регистрах TCCR1A и TCCR1B. Настройки для разрешения/запрета прерываний Timer/Counter1 находятся в регистре Timer/Counter Interrupt Mask Register (TIMSK).

Когда Timer/Counter1 тактируется от внешнего входа, внешний сигнал тактов синхронизируется с частотой тактового генератора ядра (системными тактами) CK. Чтобы гарантировать корректную выборку внешних тактов, минимальное время между двумя переходами уровня внешнего сигнала тактов должно быть как минимум одному интервалу периода CK. Внешний тактовый сигнал таймера анализируется по нарастанию уровня внутренней системной частоты CK.

16-битный Timer/Counter1 сочетает как высокую разрешающую способность (и соответственно высокую точность), так и возможность выбора различных коэффициентов деления входной тактовой частоты. Выбор высокого коэффициента деления тактовой частоты делает Timer/Counter1 полезным для низкоскоростных функций или для точной синхронизации нечасто выполняемых действий.

Timer/Counter1 поддерживает 2 функции сравнения Output Compare с использованием регистров Output Compare Register 1 A и B (OCR1A и OCR1B) в качестве источника данных для сравнения со значением счетчика Timer/Counter1. Функции Output Compare включают опциональную очистку счетчика при совпадении (compare match), и действия для выводов Output Compare для обоих сравнений (compare match A и B).

[Конфигурация аппаратуры]

Чтобы произвести длинную задержку от быстрой системной частоты тактирования CK, каскадируются прескалер и два встроенных в кристалл микроконтроллера таймера/счетчика. Конфигурация аппаратуры показана на рис. 4.

Рис. 4. Конфигурация микроконтроллера AVR для поддержки генерации длинных задержек.

Timer/Counter1 конфигурируется как таймер. Он программируется для деления системной частоты с заданным пользователем коэффициентом, и переключает вывод OC1A каждый раз, когда значение счетчика достигает значения, записанного в регистр сравнения output compare (OCR1AH-OCR1AL). Когда выход OC1A переключается, регистр Timer/Counter1 (TCNT1H-TCNT1L) перезагружается нулевым значением, и начинает счет сначала.

Поскольку переключающийся выходной порт OC1A подключен ко входу T0 счетчика, то частота на выходе OC1A может создавать тактирование для Timer/Counter0. Последний конфигурируется как счетчик, и инкрементируется по каждому нарастающему фронту на входе T0. Тогда Timer/Counter0 переполняется, устанавливается соответствующий флаг в TIFR, и происходит прерывание. Этот момент говорит о том, что запрограммированная задержка истекла.

Самая большая задержка может быть вычислена при следующих условиях:

Максимальный коэффициент деления прескалера для Timer1: 1024
Максимальный коэффициент деления для Timer1: 65536
Переключение OC1A подразумевает дополнительный коэффициент деления на 2
Максимальный коэффициент деления для Counter0: 256.

Для общего случая величина программируемой задержки может быть вычислена по следующей формуле:

T = 2 / CK * T1P * OCR1A * (256 – TCNT0)

CK = системная тактовая частота
T1P = коэффициент деления прескалера, заданны в TCCR1B (может быть либо 8, либо 64, либо 256, либо 1024).

Эта короткая программа показывает, как сконфигурировать таймеры/счетчики для реализации задержки на 1 час при работе от системной тактовой частоты CK = 1 МГц (вычислительная мощность аналогична 80C51). Во время фазы запуска конфигурируются таймеры/счетчики и контроллер прерываний. Затем происходит вход в режим сна (idle mode). После истечении часа произойдет прерывание. Это событие разбудит ядро, которое запустит выбранную программистом задачу. В этом примере для простоты только лишь переключается ножка выходного порта (PA0). Когда эта задача завершена, ядро снова входит в режим сна. Этот цикл продолжается бесконечно. Ниже приведен прокомментированный исходный код.

В этот раз рассмотрим вопросы реализации задержек и использования базовых таймеров. Также слегка зацепим систему тактирования STM32F4.

В первом уроке мы мигали светодиодом, используя некую программную задержку, суть которой сводилась к тому чтобы гонять пустой цикл for (; nCount!=0; nCount—). Как ни странно, но перевести «мартышек в попугаи», т.е. циклы в миллисекунды или микросекунды удалось далеко не сразу. На форумах звучала цифра «около 10 тактов» за один проход цикла, т.е. при тактовой частоте 16МГц это будет (1/16 000 000)*10=0,625 мкс. Практические замеры подтвердили правильность результатов.

В итоге, в одной из библиотек, удалось откопать функцию, позволяющую использовать задержки в миллисекундах:

Для проверки работы функции используем код ногодрыга, постоянно инвертируем ножку с задержкой в 10мс, т.е. осциллограф должен показать меандр с частотой 1000/10*2=50Гц

Читайте также:  Использованный дисплей не подключен к гп nvidia

#include #include #include vo >

Результат, при работе от внутреннего генератора на 16МГц,

Как видно задержка 10.1мс вместо 10, поэтому пришлось откопать кусок кода, который позволяет перевести работу камня от внешнего кварца. Тут нужно быть внимательным, ибо тактовая частота в кокосе настраивается в файле stm32f4xx.h о чем будет сказано чуть ниже.

RCC->CR|=((uint32_t)RCC_CR_HSEON); while (!(RCC->CR & RCC_CR_HSERDY)) <>; //ожидание готовности RCC->CFGR &=

RCC_CFGR_SW; //сброс битов RCC->CFGR |= RCC_CFGR_SW_HSE; // Выбрать HSE для тактирования SW0=1.

При работе, от внешнего генератора результат порадовал:

Можно сделать вывод, что функция работает, задержку выдает исправно, нужны точные задержки — юзай кварц, если точность не нужна то достаточно внутреннего RC генератора.

Кстати, iEugene0x7CA провел подобный эксперимент, демонстрирующий работу AVR от внутреннего RC генератора и от внешнего кварца и любезно поделился результатами наблюдений. В качестве опыта использовался код ногодрыга на 50Гц:

PORTB.0=1; delay_ms(10); PORTB.0=0; delay_ms(10);

Осциллограмма работы подобного кода, при работе от внутреннего RC генератора и температуре +20С:

Тоже самое, только после охлаждении микроконтроллера сжиженным газом &#128578;

Как видим частота «уплыла» почти на 1Гц. Ну и на закуску работа от кварца:

Поэтому пользуйтесь delay с пониманием происходящего.

Перейдем к использованию таймеров, задача прежняя — подрыгать ножкой с определенной частотой. В STM32F4 имеется несколько типов таймеров, пока рассмотрим так называемые Basic. В документации мы видим, что к ним относятся TIM 6 и 7.

Это самые простые таймеры, они умеют тикать и вызывать прерывание по совпадению. Так как они 16 битные, то умеют тикать от 0 до 2^16 = 65 536. Идея прерывания по совпадению аналогична AVR, т.е. когда таймер досчитает до определенного значения, которое мы определяем сами, происходит прерывание, в котором можно определять необходимые дальнейшие действия.

Также, как и в ситуации с ножками, прежде чем использовать таймер нужно включить тактирование шины, на которой он сидит, а сидит он на APB1. Включить тактирование можно, с помощью регистра RCC APB1 peripheral clock enable register (RCC_APB1ENR). Включить тактирование TIM6 можно так:

Но самый прикол начинается в тот момент, когда нужно определить на какой частоте работает APB1, тут можно запросто отхватить опухоль мозга. Далее я приведу небольшие наброски того, что удалось понять на текущий момент, естественно система тактирования достойна отдельного рассмотрения, вероятно в ближайшем будущем это будет. Но совсем пропустить этот момент сейчас нельзя, ибо тут слишком все взаимосвязано, поэтому считаем это предварительным ознакомлением.

Поехали разбираться. Даташит на этот счет выдает такую вот общую схемку:

После нескольких дней курения форумов и даташитов, имеется следующая картина: в качестве основного источника может выступать HSE (e-external) внешний генератор (кварц 4-26МГц) и HSI(I-internal) — внутренний (RC на 16МГц). Похоже на AVR. Но есть особенность, называемая PLLCLK, не влезая в подробности эта система позволяет умножить базовую частоту, в качестве которой берется HSE или HSI, таким образом получаются заветные 168МГц.

Разобраться с системой тактирования таймеров не так просто. Как видно, сигнал от основного источника подается на шину AHB, где ее поджидает местный prescaler(делитель) 1,2.. 512. Далее используется делитель APB, причем если делитель не равен 1, то частота умножается на 2. Этот момент нужно учитывать, ибо это местная фича.

Окончательно выносят мозг пояснения к этой таблице:
Когда TIMPRE бит в регистре RCC_DCKCFGR сброшен и APBx предделитель 1, тогда TIMxCLK = PCLKx, в противном случае TIMxCLK = 2x PCLKx.
Когда TIMPRE бит регистра RCC_DCKCFGR установлен и APBx предделитель 1,2 или 4, тогда TIMxCLK = HCLK, в противном случае TIMxCLK = 4x PCLKx.

В итоге главное понять, что максимальная частота AHB 180МГц, APB2 90МГц, а APB1 45МГц. Хоть как то прошарить эту тему поможет фирменная софтина от STM, которая поставляется в виде экселевского файла. Она, в том числе, поможет сгенерить файл system_stm32f4xx.c в котором выставляются все настройки частоты и прескалеров. Настоятельно рекомендую скачать ее и ознакомиться, дабы просто составить общее впечатление того, как работает механизм тактирования.

Заканчивая разговор о тактовой частоте нужно запомнить, что камень всегда стартует на внутреннем генераторе, даже если используется внешний кварц. Переключиться на внешний кварц можно тем куском кода, что был приведен выше. Для простоты работы прескалеры не трогаем, тактовую частоту выставляем в файле stm32f4xx.h в строках HSI_VALUE и HSE_VALUE. При таких настройках таймер будет тикать на основной частоте, т.е. либо 16МГц внутренних, либо 8МГц внешних.

Попробуем подрыгать ногой PD12, при помощи таймера с частотой 50Гц.

#include #include #include int main(void) < //Работаем от внешнего кварца 8МГц RCC->CR|=((uint32_t)RCC_CR_HSEON); while (!(RCC->CR & RCC_CR_HSERDY)) <>; RCC->CFGR &=

RCC_CFGR_SW; // Очистить биты SW0, SW1. RCC->CFGR |= RCC_CFGR_SW_HSE; // Выбрать HSE для тактирования SW0=1. //Настройка таймера 6 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6,ENABLE); //включить тактирование таймера 6 TIM6->PSC = 8000 – 1; //Настройка делителя на 1000 "тиков" в секунду, ибо основная частота 8МГц TIM6->ARR = 10-1; //Регистр сравнения, досчитали до 10 – вызвали прерывание, т.д. каждые 10мс TIM6->DIER |= TIM_DIER_UIE; //Разрешаем прерывания от таймера TIM6->CR1 |= TIM_CR1_CEN; //Включить отсчет таймера NVIC_EnableIRQ(TIM6_DAC_IRQn); //Разрешение прерывания TIM6_DAC_IRQn //настройка ножки GPIO_InitTypeDef GPIO_InitStructure; //Структура содержащая настройки порта RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE); //Включаем тактирование порта D GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; //Выбираем нужные выводы GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; //Включаем режим выхода GPIO_Init(GPIOD, &GPIO_InitStructure); //вызов функции инициализации while(1) <> > void TIM6_DAC_IRQHandler(void) < TIM6->SR &=

TIM_SR_UIF; //Сбрасываем флаг прерывания GPIO_ToggleBits(GPIOD, GPIO_Pin_12); //инвертируем состояние ножки >

Может показаться, что исходник большой и не понятный, но если внимательно разобраться то ничего сложного нет. Настраивается предделитель таймера, чтобы таймер тикал нужное количество раз за секунду, в регистр сравнения пихаем нужное количество отсчетов, разрешаем обработку прерывания от таймера, запускаем таймер. Все это было в AVR ничего нового. Внутри прерывания инвертируем ножку.

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

При работе от кварца

Статья получилась довольно сумбурной, кардинально переписывал ее 7 раз, в итоге все равно как капля в море, ибо в отличии от AVR, работа с STM32 без даташита не мыслима. Если расписывать все тонкости настройки регистров, то тут никаких статей не хватит. Поэтому будем учиться постепенно, пока это только первые шаги. К таймерам мы еще вернемся.

12 комментариев: Базовые таймеры. Реализация задержек.

Привет!
TCCR1B |= (1 AHB1ENR |=RCC_APB1ENR_TIM2EN;

TIM2->PSC|= 8000 — 1; // Настраиваем делитель что таймер тик
TIM2->ARR|= 10-1 ;
;
TIM2->DIER |= TIM_DIER_UIE;
TIM2->CR1|= TIM_CR1_CEN;
NVIC_EnableIRQ (TIM2_IRQn );
Не работает через регистры.А в stm32f103b работает значит чегото не хватает хотя я документацию смотрел это режим internal clock. А через куб работает?

Если хочется через регистры, то тут стандартные советы, посмотреть документацию, посмотреть примеры, посмотреть куб/spl. У стмок есть небольшие отличия в периферии в разных сериях, так что тут надо на конкретный пример смотреть.

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

admin

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *