======Таймеры общего назначения====== В данной статье приведена общая информация по таймерам, которая поможет освоить соответствующий раздел официальной спецификации. Таймеры в различных микроконтроллерах несколько отличаются, в данной статье будем ориентироваться на обобщенный таймер с максимумом возможностей. Учитывайте, что в таймерах каких-то микроконтроллеров некоторых опций может не быть. =====Общая информация===== В каждом микроконтроллере присутствует несколько таймеров общего назначения. Например, в 1986ВЕ9х реализовано три 16-ти битных таймера, а в 1986ВЕ1Т четыре 32-битных таймера. Каждый таймер обеспечивает свой счет и генерацию прерываний согласно настройкам. В каждом таймере существует по 4-ре канала, каждый из которых может независимо работать как в режиме ШИМ (генерация импульсов), так и в режиме захвата. Для реализации этих режимов используются выводы GPIO, настроенные как выход для ШИМ и как вход для захвата внешнего сигнала. В режиме ШИМ используется от одного до двух выводов GPIO, настроенных как выход. На один вывод, называемый //"прямым"//, подается генерируемый таймером сигнал заданной скважности. На второй вывод, называемый //"инверсным"//, выводится сигнал, инверсный относительно прямого вывода. Инверсный вывод используется не всегда, в основном он необходим для моторов, требующих такого управления. При использовании обоих выводов иногда требуется разнести во времени фронты переключения прямого и инверсного сигналов, чтобы исключить протекание сквозного тока в момент общего переключения. Для этого используется контроллер "мертвой зоны", который задает необходимую задержку между фронтами. Когда инверсный вывод не используется, то вывод GPIO для него не занимается. При управлении моторами так же бывает необходима функция аварийной остановки, когда таймер перестает выводить ШИМ, а выводы переводятся в дефолтный уровень. Для этого в таймере есть внешние входы BRK и ETR, они так же должны быть назначены на GPIO в случае надобности. BRK - это чисто аварийный вход, а ETR (External Timer Reference) помимо аварийного использования служит для задания внешней частоты счета. Подробнее про частоту счета в [[#Режим счетчика|"Режим счетчика"]]. Необходимо отметить, что BRK останавливает ШИМ асинхронно, а ETR - синхронно с частотой тактирования таймера. В режиме захвата используется только один внешний вывод - прямой, настраивается он как вход. В этом режиме счетчик записывает в регистрах CCR и CCR1 значение регистра CNT в момент обнаружения на входе фронта или спада внешнего сигнала. Необходимо отметить, что **каждый таймер работает на своей тактовой частоте TIM_CLK**, которая формируется из частоты ядра с помощью делителя (от 1 до 128, по 2n), задаваемого отдельно для каждого таймера в регистре TIM_CLOCK. Там же таймер включается. Вся работа таймера, захват и ШИМ режимы выполняются по переднему фронту сигнала TIM_CLK. Таким образом, при задании TIM_CLK равной частоте ядра достигается максимальная точность захвата фронта внешнего сигнала. То есть в если внешний сигнал поменяет уровень между фронтами TIM_CLK, то захват этого события случится на ближайшем фронте TIM_CLK, а не где-то посередине. =====Режим счетчика===== Основная задача счетчика - это считать число входных импульсов. Количество импульсов считается в регистре CNT. Для того чтобы ограничить верхнюю границу счета используется регистр ARR (Auto-Reload Register). При достижении регистром CNT значения, заданного пользователем в ARR, генерируется прерывание и счет начинается заново. Таким образом, регистр ARR является своего рода периодом, поскольку его можно использовать для генерации прерываний с заданным периодом времени. {{doc:doclist:timer_counter_ex.png}} Счет в регистре CNT может происходить не только в сторону увеличения, доступны такие режимы: * //Инкремент (СNT++)//: Счет от 0 до ARR, сброс в 0 и далее по циклу. * //Декремент (СNT--)//: Счет от ARR до 0, сброс в ARR и далее по циклу. * //Двунаправленный (СNT+-)//: Счет от 0 до ARR, затем от от ARR до 0 и далее по циклу. Прерывания могут генерироваться, когда (CNT == ARR) и когда (CNT == 0), это задается флагами в регистре IE. Событие совпадения CNT с регистрами CCR и CCR1 используется в режиме ШИМ и описано в соответствующем разделе ниже. Совпадения CNT с 0 и ARR, могут являться //"событиями счета"// для других таймеров, подключенных каскадно. Например, TIMER1 может отсчитывать 16 младших бит от общего количества входных импульсов, а TIMER2 старшие 16 бит. При этом каждое событие CNT==ARR в TIMER1, увеличивает на 1 регистр CNT в TIMER2. Таким образом, реализуется составной 32-битный таймер на основе двух 16-ти битных. Двунаправленный режим счета обычно используется для того, чтобы прерывания возникали в центре импульсов, генерируемых таймером в режиме ШИМ. Подробнее об этом далее. На самом деле значение CNT изменяется не просто по входному импульсу, а по фронту или спаду этого сигнала. Поэтому событие, по которому происходит изменение CNT будем называть //"событием счета"//. Источников входных импульсов ("событий счета") может быть несколько, все они приведены на данной упрощенной картинке: {{doc:timer_cnt.png}} //Передний фронт сигнала будем называться просто "фронт", а задний фронт называть "спад". Регистры и относящиеся к ним биты помечены одинаковыми цветами.// "Событием счета" - может выступать: * Фронт тактового сигнала TIM_CLK после делителя PSG. * Событие CNT==ARR в других таймерах. * Фронт или спад сигнала на прямых выводах каналов таймера, настроенных как вход. * Фронт или спад сигнала на входе ETR таймера. Выбор события счета и направления счета задается в регистре **CNTR**, в следующих битовых полях: ^ Событие счета ^ EventSel[8..11] ^ CntMode[6..7] ^ Dir[3] ^ Режим счета ^ | Фронт TIM_CLK | b0000 | b00 | b0 | CNT++ | | ::: | ::: | ::: | b1 | CNT-- | | ::: | ::: | **b01** | **bx** | **CNT+-** | | CNT==ARR \\ в таймерах | b000-b0011 \\ b1010 | b1x | b0 | CNT++ | | ::: | ::: | ::: | b1 | CNT-- | | Фронт или спад \\ на каналах таймера | b01хх | bxx | b0 | CNT++ | | ::: | ::: | ::: | b1 | CNT-- | | Фронт или спад \\ на входе ETR | b1000 \\ b1010 | b1x | b0 | CNT++ | | ::: | ::: | ::: | b1 | CNT-- | //Символом "х" помечены биты, значения которых не учитываются в заданном режиме. // Как видно из таблицы, направление счета, как правило, задается битом DIR. Исключением является случай счета вперед-назад, который доступен только по фронту внутреннего тактирования TIM_CLOCK. ===Счет по фронту TIM_CLK=== Регистр CNT изменяется на каждом фронте сигнала TIM_CLK, прошедшего через делитель, определяемый регистром PSG. То-есть частота счета получается CNT_CLOCK = TIM_CLK / ( PSG + 1). В спецификации для значений CNT_MODE равных b00 и b01 стоит уточнение **"при PSG = 0"**. Это не относится к значению регистра PSG, значение PSG может быть больше 0! Здесь имеется ввиду, что значение PSG делит тактовую частоту TIM_CLOCK и событие счета возникает при обнулении внутреннего счетчика (назовем его PSG_cnt), который пропускает для инкремента регистра CNT только каждый PSG-й импульс TIM_CLOCK. Т.е. регистр CNT изменяется при PSG_cnt = 0, после запускается новый цикл делителя PSG_cnt = PSG. ===Счет по CNT==ARR в таймерах=== Событие CNT==ARR в таймерах используется для объединения нескольких таймеров в общий таймер-счетчик с большей разрядностью. ===Счет по фронту или спаду в канале таймера === Когда прямой GPIO вывод канала настроен в режим захвата, то сигнал на этом входе может служить источником событий для счета. Необходимо лишь выбрать фронт или спад внешнего сигнала, как источник для изменения регистра CNT. Выбор осуществляется в поле CHxSel[4..5] регистра CHx_CNTRL. Такой режим полезен для подсчета количества каких-то внешних событий. Например, при подключении детектора фотонов, можно считать число зарегистрированных частиц с момента запуска измерения. Внешний сигнал от данного входа может быть отфильтрован, подробнее далее. ===Счет от внешнего вывода ETR === Каждый таймер имеет отдельный внешний вывод ETR, который всегда используется как вход. Фронт или спад сигнала на данном выводе могут так же быть источником изменения CNT. Вход ETR необходим, чтобы оставить каналам таймера их основную функциональность - работу в режимах ШИМ (Широтно-Импульсной Модуляции) и захвата, при этому счет будет происходить по сигналу с ETR. По существу можно сказать, что существует два основных способа задать сигнал //"тактирования счета"// (изменения CNT) - внутренний и внешний. Внутренний - это использование импульсов TIM_CLK, внешнее - это использование внешних импульсов со входа ETR. При этом "тактировании счета" каналы таймера работают в режимах захвата и ШИМ, выполняя заложенную в них пользователем функцию. Внешний сигнал от данного входа так же может быть отфильтрован. =====Фильтрация входного сигнала===== Когда таймер считает импульсы с внешнего входа, может возникнуть необходимость отфильтровать помехи и исключить ложные срабатывания при колебаниях сигнала на входе. Для этого внешние сигналы пропускаются через фильтр, который пропускает только те сигналы, которые соответствуют ожидаемой длительности импульса. Поскольку внешние импульсы могут быть заведены через входы каналов и вывод ETR, то у каждого из этих входов есть свои фильтры. Для канала таймера значение фильтра задается в регистре //CHy_CNTRL// битами CHFLTR[0..3]. А для ETR в регистре //BRKETR_CNTRL// битами FILTER[4..7]. Значение этих полей определяет, сколько тактов **//"частоты сэмплировния"//** должен продолжаться импульс на входе, чтобы считаться достоверным, а не помехой. Под сэмплированием подразумевается производное понятие от SampleRate, скорости оцифровки сигнала. То есть частота сэмплирования - это частота измерения или взятия отсчетов. Поскольку частота TIM_CLK часто настраивается в максимум для достижения наименьшей погрешности определения фронтов, то использовать ее для проверки длительности входного сигнала неудобно. Потребуется счетчик большой разрядности, чтобы замерять в тактах TIM_CLK относительно длинные входные импульсы. Для упрощения счета ввели производную от TIM_CLK частоту - FDTS, которая наравне с TIM_CLK используется для задания фильтрации. {{doc:doclist:timer_dts.png}} Частота FDTS в периодах TIM_CLK задается в регистре //CNTRL// в битах DTS[4..5], максимальное значение DTS[4..5] = 3 формирует частоту FDTS = TIM_CLK / 4. //(В спецификации описанные биты называются FDTS[4..5], чтоб не устраивать путаницу, здесь я частоту называю FDTS, а управляющие биты DTS.)// Следующая картинка показывает счет события по переднему фронту входного сигнала с фильтрацией. {{doc:doclist:timer_filter.png}} **ВАЖНО!** //В данном случае на картинке представлен гипотетический вариант, когда сэмплирование происходит в 2-х триггерах на частоте FDTS / 2. Ближайшее реально возможное значение CHFLTR[0..3] = b0100 задает режим сэмплирования в 6 триггерах на частоте FDTS / 2, но этот вариант сложно отобразить на картинке, она получится слишком длинной. Вариант же сэмплирования от TIM_CLK представлен в спецификации и не так нагляден. // Данная иллюстрация показывает, что: - Вся работа в таймере происходит синхронно с фронтом сигнала TIM_CLK - красные линии. - Источник частоты сэмплирования и фильтрация входного сигнала выбрается в битах CHFLTR[0..3] регистра CHy_CNTRL. В данном случае используется сэмплирование от частоты FDTS. - Частота сэмплирования FDTS задается в DTS[4..5] регистра CNTRL. - Событие счета выбирается в битах CHSEL[4..5] регистра CHy_CNTRL. В примере выбран фронт сигнала, так же может быть выбран спад. - Захваченное событие, в данном случае - фронт, должно сохраняться заданное число триггеров фильтра, определяемого вместе с выбором частоты сэмплирования в п.2. Импульсы меньшей длительности никак не влияют на счет счетчика. В случаем с использованием сигнала счета с ETR, картинка будет аналогичная, только выбор частоты сэмплирования и значения фильтра происходит в регистре BRKETR_CNTRL. Так же, для сигнала ETR есть дополнительный инвертор и предделитель внешней частоты. Предделитель используется тогда, когда используется внешний стабильный сигнал тактирования счета и необходимости в фильтрации коротких импульсов нет. =====Режим захвата===== Каждый канал таймера может работать в режиме захвата. В этом режиме канал "детектирует" фронты и спады сигнала на прямом внешнем выводе. Происходит это следующим образом: * Таймер считает свои отсчеты в регистре CNT. Частота счета CNT_Clock задана одним из способов описанных выше. * На частоте сэмплирования (TIM_CLK или FDTS) проверяется уровень внешнего сигнала. Если уровень изменился, например был "0", а в следующем такте сэмплирования на входе появилась "1", то генерируется событие. * По этому событию текущее значение CNT сохраняется в одном из регистров CCR или CCR1. * На каждое событие может быть разрешено генерирование прерываний. {{doc:doclist:timer_capture.png}} В регистре CHy_CNTRL в поле CHSEL[4..5] задается по какому событию CNT будет сохраняться в CCR, по фронту или по спаду. Аналогичный выбора события для CCR1 осуществляется в регистре CHy_CNTRL2 поле CHSEL1[0..1]. Делитель позволяет захватывать не каждое событие, а лишь каждое второе, четвертое или восьмое. Настройка делителя происходит в поле CHPSC[6..7] регистра CHy_CNTRL. Таким образом, в двух регистрах CCR и CCR1 "захватывается" значение CNT в тот или иной момент. Настроив CCR на фронт, а CCR1 на спад, можно узнать длительность импульса в отсчетах времени регистра CNT. Отследив изменение CCR, несложно также получить и период сигнала. Регистр CCR1 используется не всегда, он подключается опционально. Для использования CCR1 его необходимо включить в регистре CHy_CNTRL2 битом CCR1_EN[2]. При измерении сигнала для захвата могут быть использованы все настройки фильтрации, описанные выше в [[#"Фильтрация входного сигнала"]]. То есть можно сделать, чтобы фронты и спады захватывались только у импульсов определенной длительности. Инверсный вывод GPIO канала таймера в данном режиме не используется. Прямой вывод должен быть настроен как вход, в регистре CHy_CNTRL1 поле SELOE[0..1] = 0. Подробнее в описании данного поля в разделе ШИМ. =====Режим ШИМ===== Каждый канал таймера может работать в режиме ШИМ (Широтно-импульстной модуляции), попросту говоря, генерировать импульсы заданной длины при заданном периоде. Для этого так же используются регистры CCR и CCR1, которые разбивают диапазон значений CNT от 0 до ARR на необходимые интервалы. При совпадении значения CNT с одним из регистров CCR и CCR1 меняется уровень выходного сигнала Ref. Как именно изменяется сигнал Ref при совпадении регистров, настраивается в CHy_CNTRL полем OCCM[9..11]. Регистр CCR1 используется не всегда, он подключается опционально. Для использования CCR1 его необходимо включить в регистре CHy_CNTRL2 битом CCR1_EN[2]. {{doc:doclist:timer_pwm.png}} Сигнал Ref - это внутренний сигнал, он может непосредственно подаваться на выводы канала таймера - прямой и инверсный, либо пропускаться сперва через блок DTG и затем подаваться на выводы. При стандартных настройках на инверсный вывод автоматически подается сигнал инвертированный относительно прямого вывода. За подачу сигналов на прямой и инверсный выводы отвечает регистр CHy_CNTRL1, здесь для каждого из выводов своя настройка. Поле SELO[2..3] определяет какой сигнал подается на выход. ^ SELO[2..3] ^ выход GPIO ^ | 00 | "0" | | 01 | "1" | | 10 | Ref | | 11 | RefDTG | RefDTG - это сигнал Ref прошедший блок DTG. Помимо сигналов ШИМ, может выводиться постоянный уровень "0" или "1". Бит INV[4] позволяет инвертировать выходной сигнал. В блоке управления портами GPIO есть регистр OE (Output Enable), где каждый бит в регистре разрешает выводу GPIO работать на выход, или нет - [[https://startmilandr.ru/doku.php/doc:doclist:gpio_schm|"Схема выводов GPIO"]]. При настройке вывода в функцию Port (Func = 0), этот регистр OE задается программно. Если же функция назначает пин для использования какому-то блоку, например UART, то значением OE управляет сам периферийный блок. В таком случае блок UART сам переключит вывод UART_TX в режим OUT, а UART_RX оставит с выключенным выходным драйвером. При назначении пина на каналы таймера, состояние ОЕ данного вывода необходимо задать в регистре SELOE[0..1]. Т.е. у каждого канала есть внутренний сигнал разрешения CHy_oe (nCHy_oe), этот сигнал уходит в блок GPIO и задает будет ли вывод канала являться входом или выходом. Если на CHy_oe подана "1", то канал работает как выход и выводит сигнал определенный полем SELO[2..3]. Если CHy_oe = 0, то вывод является входом. В случае работы в режиме захвата с этого вывода захватывается сигнал. В терминах спецификации это обозначено как - //"канал на выход не работает"//. Значение сигнала разрешения задается в поле SELOE[0..1]. Кроме ручного выставления "0" и "1", доступен вариант когда для сигнала разрешения используется сигнал Ref или RefDTG. В этом случае текущий уровень выбранного сигнала определяет, работает ли канал на выход или находится в Z состоянии. ^ SELOE[0..1] ^ Ref / RefDTG ^ выход GPIO ^ | 00 | - | Z | | 01 | - | SELO[2..3] | | 10 | Ref: 0 / 1 | Z / SELO[2..3] | | 11 | RefDTG: 0 / 1 | Z / SELO[2..3] | Для инверсного вывода доступны такие же поля с префиксом n - nSELO[10..11], nINV[12], nSELOE[8..9]. Назначение входов **ETR** и **BRK** было описано в самом начале статьи, они служат для аварийной остановки ШИМ. Картинка из осциллограмм ниже показывает активные уровни ETR и BRK при которых сбрасывается сигнал ШИМ. Необходимо отметить, что по умолчанию эти уровни инверсны относительно друг друга. Инверсию можно настроить в регистре BRKETR_CNTRL. Работу вывода ETR необходимо разрешить битом CHx_CNTRL.OCCE=1, для BRK дополнительных настроек не требуется. {{doc:doclist:timpwm_clearbybrketr.png}} =====Контроллер мертвой зоны, DTG===== Как уже упоминалось в начале статьи, мертвая зона нужна чтобы исключить протекание сквозного тока при одновременном переключении фронтов на прямом и инверсном выводах. Для того, чтобы на выводы подавался не сигнал Ref, а сигнал с DTG необходимо в регистре CHy_CNTRL1 задать поля SELO[2..3] = nSELO[10..11] = 3, при этом поля SELOE[0..1] = nSELOE[8..9] = 1. Если смотреть осциллографом вывод канала таймера, то можно заметить, что при переключении с REF на DTG сигнал, наблюдаемый на осциллографе, инвертировался. То есть, например, если при выводе REF длительность логической "1" составляла 30%, то при выводе сигнала с DTG эти 30% занимает логический "0". Это несколько неожиданно, я бы ожидал увидеть тот же сигнал REF только в разнесенными фронтами, но видимо так было задумано. При этом возникает соблазн включить инвертирование в том же регистре CHy_CNTRL1 битами INV[4] = nINV [12] = 1, тогда сигнал на выходе вновь станет таким же, каким был только что при выводе REF. Но тут есть один подвох, перекрытие фронтов теперь получается совершенно иное. По этой причине все-таки для управления скважностью необходимо перестроить регистр CCR, а не использовать инверсии. Нагляднее эта ситуация представлена на следующих картинках, склеенных из осциллограмм, полученных на 1986ВЕ92У. Показаны сигналы REF и DTG на выводах при скважности 30%. Смещение DTG в данных графиках гипертрофировано для наглядности. {{doc:doclist:timer_dtg.png}} На данной картинке видно, что на выходе прямого и инверсного выхода каналов не случается ситуации, когда логическая "1" присутствует на обоих выводах. {{doc:doclist:timer_dtg_inv.png}} В данном примере, все отличие настройки канала таймера состоит в том, что включена инверсия выводов. Как видно, здесь происходит перекрытие зон логической "1". Для задания фазы сигнала DTG по сравнению с обычным Ref необходимо выбрать частоту, в периодах которой будет задаваться сдвиг. Эта частота задается полем CHx_DTG.EDTS[4], можно выбрать периоды частоты TIM_CLOCK или FDTS. Количество периодов задается полями регистра CHx_DTG.DTGx[15..8], далее это количество умножается множителем в поле CHx_DTG.DTG[3..0]. //(В спецификациях на разные МК названия этих полей DTGx/DTG взаимно перепутаны. Здесь они названы так, как описаны на 1986ВЕ1Т.) // На картинке ниже показан случай, где и частота выходного сигнала задается в TIM_Clock, и сдвиг для DTG. Поэтому, если задать сдвиг равный значению в регистре CCR2, то выходной сигнал пропадает. Это происходит, потому что сдвиг становится равен длительности высокого уровня выходного сигнала. На картинке, желтый сигнал - это сигнал Ref с канала 1 таймера CH1, а зеленый сигнал с DTG с выхода nCH2. Здесь учтено, что сигнал Ref проходя блок DTG инвертируется. А поскольку удобнее сравнивать сигналы одной полярности, то выбрана именно эта пара. (Я не заморачивался качеством сигналов, поэтому на расхождение задних фронтов не стоит обращать внимание.) {{doc:doclist:timer_dtg_clock.png}} Следующая картинка показывает, как влияют параметры CHx_DTG.DTGx[15..8] и CHx_DTG.DTG[3..0] на фазу сигнала DTG. {{doc:doclist:timer_dtg_mull.png}} =====Одновременный запуск таймеров===== Для одновременного запуска нескольких таймеров необходимо их сначала проинициализировать и запустить, а затем записью в регистр TIM_CLOCK подать частоту счета. // Тактирование для управления регистрами RST_CLK_PCLKcmd(RST_CLK_PCLK_TIMER1 | RST_CLK_PCLK_TIMER2, ENABLE); // Настройки таймеров TMR1_HW_Init(); TMR2_HW_Init(); // Запуск таймеров MDR_TIMER1->CNTRL |= 0x0001; MDR_TIMER2->CNTRL |= 0x0001; // Подача частоты счета - реальный синхронный запуск! MDR_RST_CLK->TIM_CLOCK = (3<<24); =====Регистры таймера и каналы===== В каждом таймере есть 4 канала, и каждый из них может независимо работать либо в режиме ШИМ, либо в режиме захвата. Но при этом счетчик у всех каналов один. Это наиболее наглядно видно из регистров таймера. Рассмотрим структуру, описывающую эти регистры таймера подробнее: typedef struct { // Счетчик - один на все каналы. __IO uint32_t CNT; __IO uint32_t PSG; __IO uint32_t ARR; __IO uint32_t CNTRL; // Регистры события 1. СВОЙ регистр для каждого канала! События: // ШИМ: - переключение фронта/спада на выводах канала при равенстве (CNT == CCRy). // Захват: - обнаружение фронта/спада на прямом выводе канала, присвоение (CCRy = CNT). __IO uint32_t CCR1; __IO uint32_t CCR2; __IO uint32_t CCR3; __IO uint32_t CCR4; __IO uint32_t CH1_CNTRL; __IO uint32_t CH2_CNTRL; __IO uint32_t CH3_CNTRL; __IO uint32_t CH4_CNTRL; // Регистры настройки ШИМ и "мертвой зоны" (DTG). __IO uint32_t CH1_CNTRL1; __IO uint32_t CH2_CNTRL1; __IO uint32_t CH3_CNTRL1; __IO uint32_t CH4_CNTRL1; __IO uint32_t CH1_DTG; __IO uint32_t CH2_DTG; __IO uint32_t CH3_DTG; __IO uint32_t CH4_DTG; __IO uint32_t BRKETR_CNTRL; // Регистры статуса, настройки прерываний и DMA __IO uint32_t STATUS; __IO uint32_t IE; __IO uint32_t DMA_RE; // Регистры события 2. СВОЙ регистр для каждого канала! События: // ШИМ: - переключение фронта/спада на выводах канала при равенстве (CNT == CCRy1). // Захват: - детектирование фронта/спада на прямом выводе канала, присвоение (CCRy1 = CNT). __IO uint32_t CH1_CNTRL2; __IO uint32_t CH2_CNTRL2; __IO uint32_t CH3_CNTRL2; __IO uint32_t CH4_CNTRL2; // Регистры CCRy1 __IO uint32_t CCR11; __IO uint32_t CCR21; __IO uint32_t CCR31; __IO uint32_t CCR41; // ТОЛЬКО для 1986ВЕ1Т - Отдельная настройка запросов DMA для каждого канала. // В DMA_RE - настройка для все каналов сразу. __IO uint32_t DMA_REChx[4]; } MDR_TIMER_TypeDef; ====Регистры DMA_RE и DMA_REx==== C регистрами DMA_RE и DMA_RE1,2,3,4 ситуация несколько запутанная. Есть отдельные DMA каналы работающие по запросам от самого таймера и от каналов таймера. На примере Timer1: * DMA_RE разрешает вызовы к DMA каналу TIM1_DMA_REQ * DMA_RE1 отвечает за TIM1_DMA_REQ1 * DMA_RE2 отвечает за TIM1_DMA_REQ2 * и т.д. Соответственно DMA канал TIM1_DMA_REQ настраивается через регистр DMA_RE, и все разрешенные события в DMA_RE вызывают запрос к DMA по одному каналу TIM1_DMA_REQ. Чтобы как-то разделить события на разных каналах добавили аналогичные регистры DMA_REх(1,2,3,4), и теперь можно получить отдельный запрос к DMA от выбранного канала таймера. Т.е. события разрешенные в DMA_RE1 будут вызывать запросы к каналу TIM1_DMA_REQ1. В 1986ВЕ9х дополнительных каналов DMA TIMх_DMA_REQх нет, поэтому нет и регистров DMA_RE1,2,3,4. {{doc:doclist:ve1_dma_rex.png}} =====Особенности программирования===== При знакомстве с программированием блока таймеров открылись некоторые нюансы, которые чтобы не забыть представлены ниже. ====Биты WR_CMPL не сбрасываются если не подана частота TIM_CLOCK==== В регистре **CNTRL** есть бит **WR_CMPL**, который при значении "0" разрешает запись в регистры //CNT, ARR, PSG//. Значение "1" означает, что идет запись. Аналогичный бит есть в регистре **CHy_CNTRL.WR_CMPL**, который отражает завершенность записи в регистры //CCR и CCR1//. Оказалось, что биты WR_CMPL не сбрасываются вообще, если не подана частота TIM_CLOCK на блок таймера. Частота TIM_CLOCK подается через разрешающий бит регистра TIM_CLOCK.TIMx_CLK_EN в блоке тактовых частот. Скорее всего это объясняется с тем, что в таймере есть теневой набор регистров, с которым собственно и происходит счет. А значения основных регистров применяются в теневые, например как ARR при немедленном изменении или при окончании текущего периода счета. Для передачи значений основных регистров в теневые необходима рабочая частота счета - TIM_CLOCK, и если частота не разрешена, то флаг WR_CMPL остается взведенным. Если, например, в регистр CNT записать подряд несколько значений, то они записываются и считываются из этого регистра правильно. Но это не означает, что записи прошли в исполнительную часть и повлияли на работу таймера. Для отслеживания того, что параметр применился необходимо отслеживать флаг WR_CMPL, и только после этого делать новую запись. Кроме этого, вероятно возникновение проблем, если ПО будет писать в CNT, пока значение из CNT передается в теневой регистр. Поэтому, при поданной частоте TIM_CLOCK на блок таймера регистры CNT, ARR, PSG, CCR, CCR1 лучше писать при проверке WR_CMPL. Особенно если частота исполнения команд, т.е. частота ядра, сильно выше частоты таймера TIM_CLOCK. Ядро может выполнить несколько операций записи в регистры, тогда как периферийный блок не отработает и одну. ====Проблема с синхронным стартом==== При написании тестов под свой [[https://github.com/StartMilandr/MDR_Pack_v6/tree/master/PACK_Gen/Files/Examples/All_Boards/Timer_Test|Pack]] я столкнулся с тем, что при синхронном перезапуске нескольких таймеров они стартуют не синхронно, если внести изменения в направление счета между стартами. Один из трех таймеров начал работать в противофазе. Это был тот таймер в котором я поменял направление. Судя по всему начало нового счета началось в разных направлениях, стартуя со значений теневых регистров, хотя и был выставлен режим немедленного обновления. Напомню, что возможность синхронного старта таймеров реализуется одновременной подачей им частоты TIM_CLOCK. Соответственно до этого частоты должна быть выключена, а таймеры включены. При подаче TIM_CLOCK начинается синхронный счет. Поэтому для решения проблемы, необходимо * настроить таймеры при поданной частоте TIM_CLOCK * отключить TIM_CLOCK * включить необходимые таймеры битом EN * Записью в регистр MDR_CLOCK->TIM_CLOCK подать TIM_CLOCK одновременно на все выбранные таймеры. ====При выставлении CNTRL.CNT_EN в 1 выставляются события статуса==== При включении таймера сразу же выставляются биты CNT_ZERO и CNT_ARR, регистр STATUS равен 0x3. С регистром CNT это ожидаемо, поскольку обычно он инициализируется в 0 перед стартом счета. Но CNT не равен ARR, поэтому выставление бита CNT_ARR выглядит неожиданно. Кроме этого, даже если например выставить CNT=1, то все равно при включении таймера CNT_EN=1 выставляются биты CNT_ZERO и CNT_ARR. Поэтому необходимо учитывать, что если на момент включения таймера будут разрешены прерывания от событий CNT_ZERO и CNT_ARR в регистре IE и в NVIC, то сразу же сработает обработчик прерывания. Поэтому прерывания лучше настраивать после включения таймера, по крайней мере в NVIC. А активные флаги в статусе сразу же стереть (STATUS = 0), если не требуется их обработка. =====Бит CAP_FIX===== Событие захвата происходит по фронту тактовой частоты таймера TIM_CLOCK. Если на некотором фронте сигнал на входе канала поменял значение, например, был логический "0" а стал "1", то генерируется сигнал возникновения события, который уходит на NVIC, DMA и прочих абонентов. На следующем такте TIM_CLOCK значение CNT сохраняется в регистры CCR и/или CCR1 и служит значением, при котором было зафиксировано событие. Но частота TIM_CLOCK может быть много меньше чем частота ядра. Если NVIC получит сигнал на прерывание от события, то ядру Cortex-M3 потребуется 12 тактов на то, чтобы войти в прерывание и начать исполнять код. Если этот код считывает значение CCR, то это значение может оказаться старым, еще не обновленным, потому что такт TIM_CLOCK еще не прошел. Этот вариант событий наблюдается в проекте [[https://github.com/StartMilandr/Bugs/tree/master/Timer_CapSyncChannels|"Задержка обновления регистров захвата CCR в таймерах"]]. В этом проекте один канал захватывает фронт внешнего сигнала, а второй канал захватывает само событие захвата на первом канале. В прерывании считываются значения CCR обоих каналов и, теоретически, они должны отличаться не больше чем на единицу. Но если сделать частоту TIM_CLOCK небольшой, то возникают события, когда CCR одного из каналов имеет еще старое, не обновленное значение. Получается так, что прерывание возникает от первого события по захвату фронта сигнала, а через такт TIM_CLOCK будет захвачено "событие захвата в первом канале". Но обработчик прерывания, сработавший по первому событию, уже может считать флаги событий в обоих каналах, хотя CCR во втором канале еще не обновился. Т.е. флаги показывают, что значения захвата можно считывать, но CCR еще не обновилось. Если же поднять частоту TIM_CLOCK, то такого поведения не возникает - обработчик не успеет добраться до регистров CCR раньше, чем отработает обновление в таймерах. Чтобы избежать подобного сценария в регистры CHx_CTRL2 таймеров добавлен **4-й бит**, который мы называем **CAP_FIX**, а в спецификациях он еще не описан. При выставлении этого бита в "1", выставление флагов и генерация сигналов событий происходит синхронно с обновлением регистров CCR. Т.е. если стоит флаг события захвата, то можно считывать регистр CCR, значение в нем заведомо обновлено. В микроконтроллерах: * 1986ВЕ1Т, 1986ВЕ3Т и 1986ВЕ9х - бит CAP_FIX есть, помогает обойти проблему * 1986ВЕ3Т - проблема вообще не наблюдается, воспроизвести не удалось. * 1901VC1, 1986ВК234 - выставление бита не помогает обойти проблему, вероятно бита нет. * 1986ВК214 - есть только один канал таймера, данный проект не подходит. Скорее всего здесь все аналогично 1986ВК234. Подобный проект реализован под новый Pack_v6 -[[https://github.com/StartMilandr/MDR_Pack_v6/tree/master/PACK_Gen/Files/Examples/All_Boards/Timer_CapFix|GitHub]]. В нем есть Excell файл который показывает при каком делителе TIM_CLOCK_BRG возникают проблемы и как на них влияет выставление бита CAP_FIX. =====Сброс флагов STATUS===== Проверенный, потокобезопасный сброс флагов в регистре STATUS от [[https://forum.milandr.ru/viewtopic.php?p=26886#p26886|от Vasili с форума]] Для сброса флагов не надо применять чтение-модификацию-запись, включая bit-band. Достаточно записать число с нулями в битах, которые необходимо сбросить. При записи в регистр срабатывают только нулевые биты, единичные биты не изменяют текущих бит статуса. uint32_ty stat = MDR_TIMER1->STATUS; ... обработка флагов stat // Вместо MDR_TIMER1->STATUS &= ~stat; // Использовать MDR_TIMER1->STATUS = ~stat;