Самописный таймер в виде функции для промышленного контроллера Simatic S7-1200
Еще для серии S7-300 и S7-400 под Step 7 классических версий предлагаемых разработчику таймеров вполне хватало — это и стандартные таймеры IEC, реализованные в виде функциональных блоков, и таймеры S5 (которые, к слову, до сих пор существуют для серии S7-1500). Однако в ряде случаев разработчик не применял стандартные инструменты и реализовывал собственные таймеры, чаще всего — в виде функций. Такие таймеры-функции необходимы были при «айтишном» подходе к программированию, в котором оперировали не отдельными экземплярами функциональных блоков технологического оборудования, с соответствующей обвязкой входов и выходов, а массивами структур. Например — массив структуры типа «дискретный вход». Или массив структуры «агрегат». Такой подход к программированию имеет право на существование, поскольку позволяет серьезно экономить рабочую память CPU, но, с другой стороны, делает программный код трудночитаемым. Стороннему программисту и с простым видом программы на LAD разобраться получается далеко не сразу, а про кучи индексов, массивов и функций их обработки — и речи не идет, тут без документации к ППО (и без поллитры, разумеется) вообще никуда.
Эти массивы структур, как правило, обрабатывались в функциях. В принципе, ничто не мешало делать обработку и в функциональных блоках, но всегда вставал важный вопрос — как работать с таймерами в этих случаях? Стандартные таймеры предполагают либо номер (S5), либо экземпляр функционального блока (IEC). Речь, напоминаю, идет об обработке массивов структур для классических ПЛК Simatic, и «вкрячить» в эти структуры еще и номера таймеров, а тем более — экземпляры — либо сложно, либо просто невозможно.
По этой причине и создавался собственный функционал таймера в виде функции. В принципе, для работы любого таймера необходимо знать всего несколько вещей — состояние входа, уставку времени и сколько времени уже прошло с момента активации.
Для 300 и 400 серии определить это время можно было двумя способами. Первый — смотреть время выполнения главного OB1 (есть соответствующая переменная в самом OB1) или циклических OB и увеличивать внутренний аккумулятор времени при каждом вызове таймера при условии подачи «истины» на вход. Не очень хороший вариант, поскольку это время отличается для OB1 и циклических OB. Второй способ — системная функция TIME_TCK, которая при каждом вызове возвращала одно-единственное значение — внутренний счетчик миллисекунд центрального процессора.
Таким образом, для таймера типа TON (задержка включения) алгоритм работы был таков:
- по переднему фронту запроса срабатывания сбрасываем выход и запоминаем текущее значение системного таймера TIME_TCK
- если на вход запроса продолжает поступать «истина» определяем текущее значение системного таймера и вычитаем из него значение, которые мы запомнили на этапе запуска таймера (не забываем при это, что TIME_TCK возвращает значение от 0 до (2 ^ 31 — 1), а при превышении верхнего порогового значения начинает отсчет с нуля). В результате получили, сколько миллисекунд прошло с момента активации отсчета времени. Если прошло меньше заданного, подаем на выход «ложь», в противном случае — «истину»
- если на вход запроса приходит «ложь», обнуляем выход таймера
Линейка базовых контроллеров S7-1200 основана на другой архитектуре, и в ней есть ряд отличий от S7-1500. В том числе — отсутствие системного вызова TIME_TCK. В рядах разработчиков, не обладающих достаточной гибкостью мышления, пошло недовольство — невозможно выполнить копи/паст старых программ. Тем не менее, поставленную задачу определения, сколько времени прошло с момента предыдущего вызова, можно выполнить, используя функцию runtime.
Данная функция возвращает время, прошедшее с ее предыдущего вызова, в секундах в виде вещественного числа двойной точности LREAL. Подробности описаны в справке. Для внутренних целей необходимо дополнительная переменная MEM (тоже типа LREAL).
Приведу исходники первого приближения функции, и дам некоторые примечания.
FUNCTION "PerversionTON" : Void < S7_Optimized_Access := 'TRUE' >VERSION : 0.1 VAR_INPUT IN : Bool; // Вход таймера PT : Real; // Уставка времени в секундах END_VAR VAR_OUTPUT Q : Bool; // Выход таймера END_VAR VAR_IN_OUT INPrv : Bool; MEM : LReal; TimeACC : UDInt; END_VAR VAR_TEMP udiCycle : UDInt; udiPT : UDInt; END_VAR
Со входами/выходами все ясно: IN, Q и PT. Уставку времени завел в виде вещественного, это секунды. Просто так захотелось (а зря, но об этом ниже). Далее о переменных области InOut. Поскольку у нас именно функция, то у нас нет области STAT, нет переменных, которые сохраняют свое значение при последующем вызове функции, а такие переменные необходимы:
INPrv — для определения положительного фронта запроса
MEM — вспомогательная переменная для работы системного вызова runtime
TimeACC — аккумулятор времени, который будет хранить количество микросекунд идущей в настоящий момент задержки срабатывания.
Переменные TimeACC, udiCycle и udiPT заданы в формате UDINT, беззнаковое целое, 4 байта. Несмотря на то, что и задание времени я указал в виде вещественного, и функция runtime возвращает вещественное аж двойной точности, я предпочту выполнять простые операции суммирования и сравнения целочисленными операндами для экономии процессорного времени. Время в моем случае учитывается с точностью до микросекунды. Причина проста — если огрублять время до миллисекунды, то при почти пустом OB1 (например, если во всей программе контроллера вызывается только один таймер и более ничего) возможны «пропуски» циклов, программа иногда выполняется и за 250 мкс. Но в этом случае максимально допустимое значение аккумулятора времени составит 4 294 секунды, почти 4 295 (2 ^ 32 — 1 = 4 294 967 295). Ничего не поделать, такая «оптимизация» требует жертв.
#udiCycle := LREAL_TO_UDINT(RUNTIME(#MEM) * 1000000); //прошло микросекунд со времени предыдущего вызова #udiPT := REAL_TO_UDINT(#PT * 1000000); //уставка таймера в микросекундах IF (#IN AND (NOT #INPrv)) THEN //по переднему фронту входа обнулить аккумулятор времени и сбросить выход #TimeACC := 0; #Q := FALSE; ELSIF (#IN AND #INPrv) THEN //если на вход продолжает поступать "истина" #TimeACC += #udiCycle; //увеличть аккумулятор времени на величину "прошло времени с прошлого вызова" IF #TimeACC >= #udiPT THEN //если накопленное время достигло или превысило уставку #Q := TRUE; //дать выход "истина" #TimeACC := #udiPT; //зафиксировать аккумулятор временеи ELSE //если накопленное время не достигло уставки времени #Q := FALSE; //сбросить выход END_IF; ELSE //во всех остальных случаях - сбросить выход и обнулить аккумулятор времени #Q := FALSE; #TimeACC := 0; END_IF; #INPrv := #IN; //предыдущее значение запроса ENO := #Q; //выход ENO для красоты при использовании этой функции в языках LAD и FBD
Первые две строчки — пересчет уставки таймера из количестве секунд, задаваемого в формате REAL, в количество микросекунд. Так же определяется время в микросекундах, прошедшее с предыдущего вызова программного блока.
Далее алгоритм следующий, и я его уже приводил:
- по переднему фронту входа IN обнуляем выход Q и сбрасываем аккумулятор времени
- если на вход продолжает поступать «истина», увеличиваем аккумулятор времени на известную уже величину udiCycle и сравниваем его с уставкой времени. При превышении уставки времени таймер отработал, дать на выход «истину», в противном случае — дать на выход «ложь»
- в случаях подачи на вход IN значения «ложь» обнулить выход Q и сбросить аккумулятор времени.
Убеждаемся в работоспособности функции, после чего становится интересным оценить ее быстродействие и при необходимости улучшить (уже на первый взгляд становится ясно, что ряд вычислений идет в холостую и зря отнимает процессорное время). Для оценки быстродействия объявляю массив из 1000 структур данных таймера.
Объявление структуры. Её поля дублируют входные и выходные переменные функции таймера.
TYPE "typePervTONdata" VERSION : 0.1 STRUCT IN : Bool; // Вход таймера PT : Real; // Задание времени таймера Q : Bool; // Выход таймера INPrv : Bool; // Для определения фронта входа MEM : LReal; // Для вычисления прошедшего времени TimeACC : UDInt; // Аккумулятор времени END_STRUCT; END_TYPE
В глобальном блоке данных «TortureTON» объявляется массив структур:
TONs : Array[0..999] of "typePervTONdata";
В организационном блоке OB1 выполняется следующий код:
FOR #i := 0 TO 999 DO "TortureTON".TONs[#i].IN := "startton"; "PerversionTON"(IN := "TortureTON".TONs[#i].IN, PT := "TortureTON".TONs[#i].PT, Q := "TortureTON".TONs[#i].Q, INPrv := "TortureTON".TONs[#i].INPrv, MEM := "TortureTON".TONs[#i].MEM, TimeACC := "TortureTON".TONs[#i].TimeACC); END_FOR;
Объявлено 1000 «экземпляров» таймеров, у каждого задано время в 10 секунд. Вся 1000 таймеров начинает отсчет времени по значению маркерной переменной startton.
Запускаю диагностические функции контроллера (S7-1214C DC/DC/DC, версия FW 4.4, версия Step7 — V16) и смотрю время цикла сканирования контроллера. На «холостом ходе» (когда на вход таймеров поступает «ложь») вся тысяча обрабатывается в среднем за 36-42 миллисекунды. Во время отсчета десяти секунд это показание вырастает примерно на 6-8 миллисекунд и временами зашкаливает за 50 мс.
Смотрим, что можно улучшить в коде функции. Во-первых, строки в самом начале программного блока:
#udiCycle := LREAL_TO_UDINT(RUNTIME(#MEM) * 1000000); //прошло микросекунд со времени предыдущего вызова #udiPT := REAL_TO_UDINT(#PT * 1000000); //уставка таймера в микросекундах
Они вызываются всегда, вне зависимости от того, считает ли время таймер, не считает или уже посчитал. Большое расточительство — нагружать не особо мощный CPU серии 1200 расчетами, связанными с вещественными двойной точности. Разумно перенести обе строчки в часть кода, обрабатывающая отсчет времени (если на вход продолжает поступать «истина»). Так же необходимо продублировать вычисление udiCycle в код, обрабатывающий положительный фронт на входе таймера. Это должно разгрузить «холостую работу» таймера, когда на вход поступает значение «ложь». На практике таймеры в программируемых логических контроллерах чаще всего работают «на холостую». Например, время фильтрации дребезга контактов — это десятки миллисекунд. Управляющий импульс дискретного выхода — несколько сотен миллисекунд, обычно от 0.5 до 1.0 секунды. Время контроля выполнения команды агрегата (например, время полного открытия задвижки) — от десятков секунд до нескольких минут. ПЛК на производстве же работает 24 часа в сутки и 365 (а иногда и больше!) дней в году. То есть, чаще всего на входе таймера находится либо «ноль», и таймер ничего не считает, либо длительное время поступает «единица», и таймер уже все посчитал. Для разгрузки CPU холостого хода второго вида (таймер уже посчитал) необходимо на этапе «на вход продолжает поступать истина» проверять — а посчитал ли уже таймер все время и выставил ли выход в истину. В этом случае никаких вычислений выполнять не следует.
Для внесения этих изменений необходимо выход Q таймера перенести из области OUTPUT в область IN_OUT, и значение выхода будет сохраняться во внешних переменных (в данном примере — в массиве структур). После доработки весь код функции, включая объявление, выглядит следующим образом:
FUNCTION "PerversionTON" : Void < S7_Optimized_Access := 'TRUE' >VERSION : 0.1 VAR_INPUT IN : Bool; // Вход таймера PT : Real; // Уставка времени в секундах END_VAR VAR_IN_OUT Q : Bool; // Выход таймера INPrv : Bool; MEM : LReal; TimeACC : UDInt; END_VAR VAR_TEMP udiCycle : UDInt; udiPT : UDInt; END_VAR BEGIN IF (#IN AND (NOT #INPrv)) THEN //по переднему фронту входа обнулить аккумулятор времени и сбросить выход #TimeACC := 0; #Q := FALSE; #udiCycle := LREAL_TO_UDINT(RUNTIME(#MEM) * 1000000); //фиксируем "базу времени" по фронту ELSIF (#IN AND #INPrv) THEN //если на вход продолжает поступать "истина" IF (NOT #Q) THEN #udiCycle := LREAL_TO_UDINT(RUNTIME(#MEM) * 1000000); //прошло микросекунд со времени предыдущего вызова #udiPT := REAL_TO_UDINT(#PT * 1000000); //уставка таймера в микросекундах #TimeACC += #udiCycle; //увеличить аккумулятор времени на величину "прошло времени с прошлого вызова" IF #TimeACC >= #udiPT THEN //если накопленное время достигло или превысило уставку #Q := TRUE; //дать выход "истина" #TimeACC := #udiPT; //зафиксировать аккумулятор времени END_IF; END_IF; ELSE //во всех остальных случаях - сбросить выход и обнулить аккумулятор времени #Q := FALSE; #TimeACC := 0; END_IF; #INPrv := #IN; //предыдущее значение запроса ENO := #Q; //выход ENO для красоты при использовании этой функции в языках LAD и FBD END_FUNCTION
После этого время выполнения улучшается: холостой ход обработки таймеров составляет 23 мс, при работающей фильтрации времени 37-40 мс.
В этом коде функции отсутствует проверка на недопустимое значение уставки таймера — отрицательную величину (при переводе вещественного в беззнаковое целое произойдет искажение уставки) или величину больше 4294.9 секунд (произойдет переполнение и искажение уставки времени). Необходимо либо контролировать значение величины PT в коде, либо доверить задачу проверки диапазона временной уставки (от 0 до 4294.9 секунд) операторской системе верхнего уровня. Проверка диапазона средствами программы PLC увеличивает время обработки примерно до 45-46 мс (а, вообще, самый правильный способ — это задавать время таймера не в формате REAL, а в формате UDINT в миллисекундах и заниматься ерундой).
Проект прикладной программы с таймером для среды TIA Portal Step 7 версии 16 доступен по ссылке.
Что такое ворота в системном таймере
Таймер — одно из важнейших для микроконтроллера устройств, которое применяется для решения множества задач: отсчёт времени, планирование действий, генерация сигналов, измерение параметров сигналов и др. Таймеры могут генерировать ШИМ-сигналы, а значит, находят применение в многочисленных областях, связанных с питанием устройств (блоки питания; конвертеры; инверторы с однофазным, трёхфазным и многофазным выходом; частотные преобразователи и т.д.). Благодаря наличию прочей разнообразной периферии (ADC, DAC, различные интерфейсы и пр.), микроконтроллер становится не просто заменой для специализированных микросхем, ориентированных на использование в устройствах питания, но и превосходит их, обеспечивая значительную гибкость за счёт возможности программирования. И даже способен конкурировать со специализированными микросхемами по цене (за счёт универсальности, микроконтроллеры производятся большими партиями, а потому имеют низкую цену, несмотря на высокую сложность).
Разработчики устройств на базе микроконтроллеров активно используют таймеры, а разработчики микроконтроллеров, в свою очередь, стараются наиболее полно удовлетворить потребности пользователей, предоставляя им в распоряжение различные по сложности и возможностям таймеры.
Так, типичный представитель микроконтроллеров STM32 содержит порядка десяти таймеров. В их число входят как простые таймеры с базовым набором функций, так и таймеры весьма сложные и многофункциональные. С одной стороны, это даёт массу возможностей для профессиональных разработчиков, с другой — превращает в кошмар изучение данного вопроса для начинающих.
В STM32 микроконтроллерах действительно много таймеров, они действительно имеют очень большое количество функций. Эти таймеры отличаются друг от друга устройством и своими возможностями, что ещё более затрудняет их изучение. Описанию таймеров посвящены сотни страниц текста в справочном руководстве. Однако, не всё так страшно. Значительно упрощает жизнь тот факт, что все таймеры в STM имеют единый принцип построения и управления. Об этом речь пойдёт далее, а в начале, для порядка, немного о таймерах в самых общих словах.
Оглавление
Таймеры в микроконтроллерах STM
Таймер TIM1 с расширенным управлением
Базовые таймеры TIM6 и TIM7
Смотрите также
Частотомер на основе микроконтроллера STM32
Частотомер на основе микроконтроллера STM32. Конвейерный принцип измерения частоты
Здесь приведены ссылки на статьи с подробным описанием некоторых таймеров. Особенно хочется выделить устройства TIM1, TIM6, TIM7. TIM1 относится к таймерам с расширенным управлением, которые являются наиболее сложными. Если хорошо разобраться с TIM1, будет легко работать со всеми остальными таймерами. TIM6, TIM7 напротив, относятся к базовым таймерам, которые являются самыми простыми и имеют минимум функций. Остальные таймеры занимают промежуточное положение по шкале «сложности». Таким образом, изучив материалы по TIM1 и TIM6, TIM7, можно составить довольно полное представление обо всём спектре таймеров, имеющихся в микроконтроллерах STM32, их устройстве и возможностях.
Введение
Таймер — устройство, отмеряющее заданный интервал времени, по окончании которого оно вырабатывает определённый сигнал.
Когда таймер является частью микроконтроллера, способом сигнализации может быть, например, прерывание. Обычно таймер работает в периодическом режиме, т.е. не останавливается после формирования сигнала, а начинает сразу же отмерять следующий интервал такой же длительности. В результате таймер генерирует сигналы (прерывания) с определённой частотой, через заданные интервалы времени.
Рис. %img:id1
Сигнальный режим подразумевает то, что таймер отмеряет заданный интервал времени, по его окончании генерирует сигнал и останавливается. Таймер может иметь аппаратную поддержку сигнального режима, либо этот режим можно легко реализовать программно (достаточно отключить таймер в обработчике прерывания или выполнить требуемые действия внутри обработчика только при первом вызове).
Технически таймер строится на основе счётчика, который подсчитает импульсы некоторого тактового сигнала. Обычно сигнал о срабатывании таймера формируется при переполнении счётчика. Как правило, имеется возможность управлять модулем пересчёта счётчика таймера (задавать количество импульсов между двумя последовательными переполнениями счётчика). Это позволяет при фиксированной частоте тактового сигнала на входе счётчика устанавливать требуемую частоту генерации сигналов от таймера. Кроме того, обычно имеется программный доступ к счётчику таймера, по текущему значению которого можно с высоким разрешением определять время, прошедшее с момента последнего переполнения счётчика.
Таймер даёт возможность микроконтроллеру и программе вести отсчёт времени, что определяет два основных направления применения:
- Для выполнения требуемых действий в заданные моменты времени.
- Для определения (фиксации) моментов, когда произошли некоторые интересующие нас события.
К первому направлению применения таймера можно отнести, например, управление внешними устройствами по заранее запланированному расписанию, в соответствии с которым в требуемые моменты времени программа посылает управляющие сигналы к контролируемым устройствам. Интервал времени между двумя соседними запланированными моментами времени может варьироваться в широких пределах — от единиц периодов тактового сигнала до практически бесконечности (часы, сутки, годы). Независимо от длительности интервала, его можно задать с высокой точностью и с очень высоким разрешением во времени — до периода тактового сигнала.
Способность таймера отмерять очень малые интервалы времени открывает возможность применения таймера для генерации сигналов: цифровых (для этого достаточно выполнять переключение состояния внешнего вывода в соответствии с законом изменения синтезируемого сигнала) или аналоговых (за счёт совместной работы таймера и DAC).
Также таймер можно использовать для организации среды выполнения программы. Речь идёт, прежде всего, о реализации многозадачности путём переключения между выполняемыми задачами по сигналу таймера.
Не менее важным является второе направление использования таймера — для фиксации момента наступления событий. Это может быть: ведение журнала событий, измерение параметров внешних сигналов (длительность импульса, период сигнала) и т.д. Сюда можно отнести, опять же, решение задач управления, теперь уже в тех случаях, когда реакция на внешний сигнал зависит от момента его поступления.
Хорошей демонстрацией возможностей таймеров является построение частотомера на таймерах. Смотрите об этом в статье «Частотомер на основе микроконтроллера STM32. Конвейерный принцип измерения частоты».
Виды таймеров в STM32
В микроконтроллерах Cortex-M, к которым относятся и микроконтроллеры STM32, всегда имеется, по крайней мере, один таймер — системный таймер, наличие которого гарантируется требованиями архитектуры. Стандартизация системного таймера на уровне архитектуры существенно упрощает его использование. Как минимум, это обеспечивает переносимость кода, использующего этот таймер между разными микроконтроллерами. Переносимость особенно важна для кода операционных систем, так что неудивительно, то, что системный таймер обычно задействован для нужд операционной системы, если она используется, конечно.
Кроме системного таймера, микроконтроллеры STM32 имеют сторожевой таймер (и даже не один). Сторожевые таймеры имеют достаточно узкую специализацию — они используются для автоматической перезагрузки в случае зависания программы.
Кроме того, микроконтроллеры STM32 имеют множество универсальных таймеров * , которые можно использовать на своё усмотрение. В отличие от системного, они являются не частью ядра, а периферийными устройствами микроконтроллера. Включают в себя следующие типы таймеров: базовые, общего назначения и таймеры с расширенным управлением. Базовые таймеры являются самыми простыми, в соответствии со своим названием имеют лишь набор базовых функций. Таймеры с расширенным управлением — самые сложные и имеют наибольшее количество реализованных функций.
* Термин «универсальный таймер» не используется в официальной документации STM, я его ввёл для собственного удобства, чтобы объединить указанные выше три тесно связанных типа таймеров.
Для таймеров в микроконтроллерах семейства STM32 используется следующая система именования: название начинается с префикса TIM, за которым следует номер таймера: TIM1, TIM2, и т.д. Важно то, что номер таймера определяет его тип. И в разных микроконтроллерах таймеры с одинаковыми номерами обычно совместимы (одинаково устроены, имеют одинаковый набор функций и управляются одинаковым образом с помощью одинаковых наборов регистров; что, однако, не освобождает от необходимости внимательного ознакомления со справочным руководством: не гарантируется полное отсутствие различий). Такой подход очень удобен, так как позволяет легко переходить от одного микроконтроллера к другому — как в смысле изучения, так и при переносе кода. Используемая в микроконтроллерах STM система именования таймеров подразумевает то, что нумерация не обязательно должна быть последовательной, она имеет пропуски, соответствующие отсутствующим в конкретной модели микроконтроллера таймерам. Поэтому, например, если микроконтроллер имеет таймер с максимальным номером 17 (TIM17), это ещё не означает, что всего имеется 17 универсальных таймеров TIM1..TIM17 — таймеры с какими-то номерами будут отсутствовать.
В качестве примера приведём перечень таймеров, которые могут быть включены в микроконтроллеры из линейки STM32F100xx и укажем их типовую принадлежность.
Имя таймера | Тип таймера |
---|---|
TIM1 | Расширенный |
TIM2 TIM3 TIM4 TIM5 * |
Общего назначения |
TIM6 TIM7 |
Базовый |
TIM12 * TIM13 * TIM14 * TIM15 TIM16 TIM17 |
Общего назначения |
* Таймеры TIM5, TIM12, TIM13, TIM14 присутствуют не во всех микроконтроллерах линейки STM32F100xx.
Наряду с используемой нумерацией таймеров, жизнь разработчику также упрощает то, что все универсальные таймеры в STM32, несмотря на имеющиеся между ними различия, построены по единому принципу и управляются сходным образом. Более того, можно обнаружить сходство даже между таймерами в STM32 и таймерами в 8-битных микроконтроллерах семейства STM8, вплоть до одинакового именования регистров управления и битов в них.
Наиболее полным набором возможностей и функций обладает таймер с расширенным управлением. Обычно микроконтроллер STM содержит хотя бы один такой таймер, он имеет имя TIM1. Некоторые модели могут иметь два или даже три таймера с расширенным управлением (TIM1, TIM8, TIM20). Так вот, хитрость в том, что все остальные таймеры можно рассматривать как упрощённые варианты таймера TIM1, в которых исключены некоторые конструктивные элементы и не реализованы некоторые функции. С программной точки зрения работа с таймером осуществляется точно так же, как с TIM1. Естественно, с учётом того, что некоторые биты, отвечающие за нереализованные функции, переходят в число зарезервированных. Может отсутствовать целый регистр, если все его биты соответствуют нереализованным в этом таймере возможностям. Так что, изучив самый сложный таймер TIM1, можно в дальнейшем работать с любым другим таймером, уточнив лишь доступный для него набор функций.
Устройство таймера
На рисунке %img:simple_dgm изображена сильно упрощённая структурная схема таймера в микроконтроллере семейства STM32. Таймер здесь представлен состоящим из блоков: времязадающий модуль (Time-base unit); схема управления (Controller); набор каналов таймера (Channels); схема останова (схема защитного отключения выходов каналов, Break). Названные блоки тесно взаимодействуют, обмениваясь сигналами управления и событий (обозначено стрелками на схеме). Таймер подключён к шине микроконтроллера APB, через которую осуществляется доступ к его регистрам, от этой же шины таймер получает тактовый сигнал, который можно использовать как внутренний тактовый сигнал для счётчика таймера (на схеме не показано).
Рис. %img:simple_dgm
Таймер имеет возможность взаимодействовать с внешним окружением, проще говоря, имеет входы и выходы (входы/выходы каналов, вход ETR, вход BKIN). Некоторые таймеры микроконтроллера могут взаимодействовать между собой: для этого у них есть по одному внутреннему выходу триггерного сигнала (TRGO) и по 4 внутренних входа для триггерных сигналов ITR0, ITR1, ITR2, ITR3. Каждый триггерный вход данного таймера подключён к триггерному выходу другого определённого таймера. А триггерный выход может быть внутренне подключён к некоторому триггерному входу одного или нескольких других таймеров. Режим использования триггерных входов/выходов таймеров зависит от заданных настроек. Далее в таблице приводится схема подключения таймеров в микроконтроллерах линейки STM32F100xx.
Подчинённый таймер | Подключение входов подчинённого к ведущим таймерам * | |||
ITR0 | ITR1 | ITR2 | ITR3 | |
TIM1 | TIM15 (TIM5) ** | TIM2 | TIM3 | TIM4 |
TIM2 | TIM1 | TIM15 | TIM3 | TIM4 |
TIM3 | TIM1 | TIM2 | TIM15 | TIM4 |
TIM4 | TIM1 | TIM2 | TIM3 | TIM15 |
TIM12 | TIM4 | TIM5 | TIM13_OC | TIM14_OC |
TIM15 | TIM2 | TIM3 | TIM16_OC | TIM17_OC |
Примечание.
* Если какой-то из таймеров отсутствует в данном микроконтроллере, то вход ITRx, который должен быть подключён к этому таймеру, будет недоступен.
** TIM5 присутствует не во всех микроконтроллерах линейки STM32F100xx. При наличии TIM5 выбор между TIM15/TIM5 выполняется с помощью средств AFIO.
Основа таймера — времязадающий модуль, главным элементом которого является счётчик. Счётчики универсальных таймеров в микроконтроллерах STM32 являются 16-разрядными (за немногочисленными исключениями: в некоторых линейках микроконтроллеров счётчики в TIM2, TIM5 могут быть 32-разрядными). Счётчик работает совместно с регистром автоперезагрузки, управляющим модулем пересчёта. При счёте вверх регистр определяет значение, до которого выполняется счёт, после достижения которого, происходит переполнение и сброс счётчика. При счёте вниз регистр определяет начальное значение счётчика, загружаемое при переполнении. Переполнение формирует специальный сигнал — событие обновления, по которому выполняется ряд важных действий, а также возможна генерация прерывания. Тактовый сигнал на счётчик поступает через предделитель с управляемым коэффициентом деления, который можно изменять в пределах от 1 до 65536 (построен на базе 16-разрядного счётчика). На вход предделителя тактовый сигнал поступает со схемы управления.
Схема управления даёт возможность выбрать источник тактового сигнала (это может быть внутренний тактовый сигнал с шины; внешний триггерный сигнал ETR; внутренний триггерный сигнал; сигнал от энкодер-интерфейса). Схема управления также определяет направление счёта (вверх или вниз), если в таймере реализована функция переключения направления счёта. Направление счёта выбирается с помощью конфигурирующих битов, а в некоторых режимах зависит от состояния таймера и переключается автоматически. При работе таймера в подчинённом режиме схема управления выбирает источник входного триггерного сигнала. Также схема управления генерирует выходной триггерный сигнал, который, в зависимости от настроек, может формироваться при переполнении счётчика таймера или в ответ на другие события.
Схема управления и времязадающий модуль — это минимум того, что входит в состав любого таймера (на рисунке эти блоки выделены цветом). Возможности таймера могут быть существенно расширены за счёт наличия одного или нескольких каналов. Каналы предназначены для выполнения наиболее часто встречающихся на практике задач по генерации импульсных сигналов и для измерения параметров внешних импульсных сигналов и могут, соответственно, работать в режиме выхода или входа. Теоретически, то, что выполняют каналы, можно реализовать программно, но аппаратная реализация разгружает процессор, увеличивает быстродействие и точность выполнения действий.
В режиме входа канал выполняет фиксацию текущего содержимого счётчика таймера по внешнему сигналу, т.е. сохраняет значение в регистре канала. Настройками можно задать источник сигнала и выбрать событие, по которому будет происходить фиксация (по нарастающему фронту или спаду сигнала). По зафиксированному в регистре канала значению, мы можем с высокой точностью узнать, в какой момент времени произошло событие. То же самое можно сделать программными средствами без использования каналов, а с использованием контроллера внешних прерываний, настроив его на генерацию прерывания в ответ на изменение сигнала на некотором входе. Тогда, считывая в обработчике этого прерывания содержимое счётчика таймера, мы определим момент, когда произошло интересующее нас событие. Но такая реализация имеет существенные недостатки:
лишняя вычислительная нагрузка на процессор, связанная с обработкой прерывания;
наличие задержки от момента наступления события до момента, когда внутри обработчика прерывания будет прочитано значение счётчика (составляет несколько периодов тактового сигнала и приводит к появлению систематической ошибки);
если прерывание не может быть обработано немедленно, величина задержки может возрасти на неопределённую величину;
максимальная частота выполнения фиксаций оказывается сильно ограничена быстродействием процессора.
В режиме выхода канал используется для генерации сигналов. В регистр канала записывают некоторое значение. Канал на каждом такте сравнивает текущее значение счётчика и значение в своём регистре и в зависимости от результата сравнения (и от настроек) изменяет состояние на своём выходе. Например, при равенстве значений может происходить формирование на выходе низкого или высокого уровня или переключение уровня на противоположный. Канал может иметь не один, а два комплементарных выхода, сигналы на которых изменяются в противофазе, с возможностью гибко задавать полярностью активных уровней этих сигналов и время задержки между сигналами при переключения. Это очень удобно при управлении мостовыми и полумостовыми схемами инверторов (задержка или «dead time» вводятся для предотвращения сквозных токов через управляемые ключи моста). Как и в случае режима входа, функции при работе в режиме выхода можно реализовать и программными средствами, но ценой потери быстродействия и точности.
При использовании таймера для генерации сигналов с ШИМ, полезным оказывается блок, предназначенный для защитного отключения выходов канала. Это отключение происходит в случае наличия сигнала на соответствующем входе микроконтроллера BKIN или в случае сбоев в системе генерации тактового сигнала микроконтроллера (включение схемы, контролируемые сигналы и их полярность определяются настройками). Защитное отключение предназначено для предотвращения повреждения управляемых силовых каскадов при возникновении аварийных ситуаций. При срабатывании защитного отключения, выходы канала переводятся в предопределённое безопасное состояние даже при отсутствии тактового сигнала микроконтроллера. Если система не используется, то при сбое в системе тактирования, выходы оказываются в статическом, «залипшем» состоянии. При использовании микроконтроллера, например, в схеме инвертора в системах электропитания, это приведёт к резкому росту тока через трансформатор и повреждению элементов схемы. Кроме того, внешний вход может быть подключён к детектору превышения тока и детектору перенапряжения на выходе.
Обзор таймеров STM32
Как уже говорилось, разные таймеры в микроконтроллерах STM32 имеют разные наборы доступных функций. Для того, чтобы было проще выбрать подходящий таймер, приведём таблицу с их основными характеристиками. Здесь рассмотрим таймеры в микроконтроллерах линейки STM32F100xx, но с учётом совместимости, информация будет полезна и для других линеек.
Таймер | Наличие входа ETR | Наличие Master mode | Наличие Slave mode | Наличие защитного отключения | Направление счёта | Наличие счётчика циклов | Количество каналов | Каналов с компл. выходами | Наличие интерфейса энкодера |
---|---|---|---|---|---|---|---|---|---|
TIM1 | + | + | + | + | +/- | + | 4 | 3 | + |
TIM2 | + | + | + | — | +/- | — | 4 | 0 | + |
TIM3 | |||||||||
TIM4 | |||||||||
TIM5 | |||||||||
TIM6 | — | + (*) | — | — | + | — | 0 | 0 | — |
TIM7 | |||||||||
TIM12 | — | + | + | — | + | — | 2 | 0 | — |
TIM13 | — | — (**) | — | — | + | — | 1 | 0 | — |
TIM14 | |||||||||
TIM15 | — | + | + | + | + | + | 2 | 1 | + |
TIM16 | — | — (**) | — | + | + | + | 1 | 1 | — |
TIM17 |
* TIM6, TIM7 имеют выходы TRGO, но они внутренне подключены к DAC (цифро-аналоговому преобразователю), так что эти таймеры не могут использоваться для управления другими таймерами.
** TIM13, TIM14, TIM16, TIM17 не имеют выходов TRGO и полноценного master-режима, но могут использоваться в качестве ведущих при совместной работе с TIM12 или TIM15, триггерными сигналами будут сигналы каналов TIM13_OC, TIM14_OC, TIM16_OC, TIM17_OC.
Все таймеры являются 16-разрядными (имеют 16-разрядные счётчики), за исключением небольшого количества моделей микроконтроллеров, в которых TIM2, TIM5 имеют 32 разряда. В линейке STM32F100xx таких устройств нет, в остальных случаях можно уточнить в документации.
Направление счёта для некоторых таймеров может быть задано программно (обозначено как «+/-» в таблице) с помощью бита DIR в регистре TIMx_CR1, для остальных таймеров счёт возможен только вверх.
Тактовым сигналом для таймера может быть внутренний тактовый сигнал с шины APB, к которой подключён таймер; выходной триггерный сигнал другого таймера (при работе в подчинённом режиме); входной сигнал одного из каналов (при наличии таковых); сигнал с интерфейса энкодера (один из вариантов подчинённого режима). Также некоторые таймеры имеют вход ETR для внешнего триггерного сигнала, который в определённых конфигурациях может быть тактовым.
Некоторые таймеры могут взаимодействовать между собой посредством внутренних подключений между триггерными выходами одного таймера и триггерными входами других. Для осуществления взаимодействия один из таймеров, между которыми имеется соединение, настраивается на работу в master-режиме, другой — в slave-режиме. Настройки таймера определяют, в каких случаях формируется импульс выходного триггерного сигнала (например, при переполнении) и какой будет реакция на входной триггерный сигнал. Таймер в одно и то же время может быть главным по отношению к какому-то таймеру и подчинённым по отношению к другому.
Кроме входа ETR, таймеры могут взаимодействовать с «внешним миром» с помощью каналов (количество каналов для каждого таймера указано в таблице). При работе в режиме выхода канал может иметь один выход или два комплементарных выхода, на которых можно получить два противофазных сигнала, что используется для генерации PWM сигналов, управляющих мостовыми или полумостовыми преобразователями.
Если таймер имеет хотя бы один канал с комплементарными выходами, он также имеет схему защитного отключения и счётчик циклов. Защитное отключение предназначено для того, чтобы в случае аварийной ситуации перевести выходы каналов таймера в безопасное для схемы предопределённое состояние, которое задаётся настройками таймера. В зависимости от настроек, схема может срабатывать при поступлении внешнего сигнала (на вход BKIN) или при обнаружении сбоев в системе тактирования микроконтроллера.
Счётчик циклов определяет, как часто будут обновляться теневые регистры таймера, что может быть полезным при генерации PWM сигналов — для того, чтобы сделать изменение параметров PWM сигнала более плавным.
Смотрите далее:
Таймер TIM1 с расширенным управлением
Системный таймер: что это и как он работает?
Системный таймер — это важная часть любой операционной системы. Он отвечает за измерение времени и поддержание точности системного времени. Без системного таймера многие операции в компьютере были бы невозможны.
Системный таймер работает на аппаратном уровне и представляет собой счетчик, который инкрементируется в каждый такт процессора. Обычно системный таймер работает с частотой 1 МГц, то есть счетчик увеличивается на единицу каждую миллисекунду.
Системное время, в свою очередь, представляет собой число, которое показывает, сколько миллисекунд прошло с некоторого начального момента. Это начальное время может быть задано в момент загрузки компьютера или при запуске операционной системы.
Системный таймер используется для множества задач, включая определение текущего времени, расчет сроков выполнения операций, синхронизацию процессов и многое другое. Он является важной составляющей работы операционной системы и позволяет ей эффективно управлять ресурсами компьютера.
Системный таймер: основная функция и принцип работы
Системный таймер – это важная компонента операционной системы, которая отвечает за учет времени и выполнение задач в заданных интервалах. Основная функция системного таймера заключается в генерации прерываний или сигналов в определенные моменты времени.
Принцип работы системного таймера основан на использовании внутреннего счетчика, который инкрементируется (увеличивается на единицу) с определенной частотой. Частота счетчика зависит от аппаратной реализации и может варьироваться в различных системах.
Когда системный таймер достигает определенного значения, например, нуля, то происходит генерация прерывания. Прерывание – это сигнал, с помощью которого операционная система может прервать выполнение текущей задачи и переключиться на выполнение другой задачи или обработать событие.
Системный таймер используется для различных целей, в том числе:
- Выполнение планирования задач – операционная система может использовать системный таймер для определения, когда следует переключиться на выполнение другой задачи;
- Организация многозадачности – системный таймер позволяет операционной системе периодически переключаться между задачами, создавая иллюзию одновременного выполнения нескольких задач;
- Измерение прошедшего времени – системный таймер может использоваться для отсчета времени выполнения задачи или прошедшего времени;
- Реакция на события – системный таймер может использоваться для активации определенных действий или обработки событий через определенные интервалы времени.
Системный таймер имеет важное значение для работы операционной системы, поскольку он обеспечивает учет времени и позволяет эффективно использовать ресурсы компьютера. Правильная настройка и использование системного таймера важно для обеспечения стабильной и безопасной работы операционной системы.
Внутреннее устройство и принципы работы системного таймера
Системный таймер (System Timer) — это специальное аппаратное устройство в компьютере, которое отвечает за определение и отсчет времени. Он представляет собой часть системной платы и может быть интегрирован в материнскую плату или существовать как отдельный микросхемный модуль.
Системный таймер используется операционной системой для контроля и организации временных интервалов, таких как задержки, периодические прерывания и измерение прошедшего времени. Он предоставляет точный и надежный источник времени для работы программ и системных процессов.
Принцип работы системного таймера основан на использовании кварцевого резонатора. Кварцевые резонаторы обладают свойством, называемым пьезоэлектрическим эффектом, при котором они могут генерировать постоянные колебания с очень высокой стабильностью.
Системный таймер использует кварцевый резонатор для создания тактовых импульсов определенной частоты. Эти импульсы затем счетчиком преобразуются в единицы времени, такие как миллисекунды, микросекунды или наносекунды.
Обычно системный таймер имеет 24- или 32-разрядный счетчик, что позволяет ему отсчитывать время в течение длительных периодов, прежде чем он достигнет своего максимального значения и начнет счет заново.
Операционная система может получать информацию о текущем значении системного таймера и использовать его для вычисления задержек и интервалов времени. Когда значение системного таймера достигает установленного предела, возникает прерывание, которое операционная система может использовать для выполнения определенных действий или переключения контекста между процессами.
Системный таймер также может использоваться программами и приложениями для выполнения задержек и измерения времени выполнения операций. Например, программа может использовать системный таймер для создания точных задержек между отправкой данных через сеть или для измерения производительности определенных участков кода.
В общем, системный таймер является важной частью компьютерной системы, обеспечивая точное измерение времени и организацию временных интервалов для эффективной работы операционной системы и приложений.
TNKernel : Системные сервисы
Системные сервисы не относятся напрямую к каким-либо объектом RTOS, они позволяют управлять системой в целом или получать текущие параметры системы. К системным сервисам относятся функция запуска tn_start_system() , функция управления карусельным планированием tn_sys_tslice_ticks() , функция обработчика системного таймера tn_tick_int_processing() , функции реализации критической секции tn_sys_enter_critical() и tn_sys_exit_critical() , функции обслуживания системного времени tn_sys_time_set() и tn_sys_time_get() .
Запуск системы
Функция запуска системы tn_start_system() обеспечивает инициализацию всех внутренних структур RTOS, создание двух системных задач и первое переключение контекста на системную задачу таймера. Фукция tn_start_system() вызывается только один раз и является функцией без возврата.
В порте TNKernel для контроллеров PIC24/dsPIC в функцию передается несколько параметров, позволяющих более гибко настроить систему (выбрать размеры стеков системных задач и т.п.). В оригинальной версии функция запуска параметров не имеет.
Диаграма запуска TNKernel изображена на рисунке:
Сервис запуска tn_start_system() как правило вызывается из функции main() .
Системные прерывания должны быть запрещены до момента вызова tn_start_system() .
Функция tn_start_system() создает две системные задачи tn_idle_task() и tn_timer_task() и запускает задачу tn_timer_task() , которая при старте последовательно вызывает две callback-функции — appl_init_callback() и cpu_interrupt_enbl_callback() . Указатели на эти функции передаются в систему в вызове tn_start_system() .
Функция appl_init_callback() служит для инициализации системного таймера, периферийных модулей, создания необходимых задач. В функции cpu_interrupt_enbl_callback() разрешается прерывание от системного таймера и после выхода из нее система начинает нормальное функционирование.
Системный таймер
Для того чтобы реализовать функции ожидания или таймауты, в системе должен присутствовать таймер, доступный планировщику. Назовем такой таймер системным таймером. Как правило, период таймера постоянен на всем протяжении работы и это период называется системным тиком. Системный тик — это минимальная единица времени доступная планировщику. Все времена, таймауты указываются именно в системных тиках а не в абсолютных единицах времени.
Обычно системный таймер реализуется на основании одного из аппаратных таймеров микроконтроллера, или таймера, являющегося частью ядра. В обработчике прерывания от этого таймера необходимо вызвать функцию tn_tick_int_processing() , которая и обеспечивает «ход» системного времени.
Функция tn_tick_int_processing() должна вызываться только в обработчике прерывания.
Управление Round-Robin
Round-Robin или карусельное планирование — это принцип переключения задач с одинаковым приоритетом при котором каждой задаче выделяется определенный квант времени (с дискретностью один системный тик). После того как задача отработает свой квант, планировщик запускает следующую в очереди готовых к выполнению задачу.
В TNKernel включен сервис tn_sys_tslice_ticks() , позволяющий настраивать длительность кванта выполнения для каждого приоритета или отключать карусельное планирование.
Карусельное планирование отключено по умолчанию для всех приоритетов.
Запрещение переключения контекста
Критическая секция это часть задачи, в которой осуществляется доступ к разделяемому ресурсу. Если один и тот же ресурс (например, глобальную переменную) используют две или более задач, критические секции называют конкурирующими. В этом случае необходимо защитить критическую секцию таким образом, чтобы доступ к ресурсу являлся атомарным.
Один из способов защиты критической секции — это использование мютекса. Однако часто мютекс является избыточным объектом для реализации критической секции и в этом случае используют парные функции запрещения и разрешения переключения контекста.
В TNKernel для PIC24/dsPIC переключение контекста запрещает функция tn_sys_enter_critical() и разрешает функция tn_sys_exit_critical() .
Вызов функций tn_sys_enter_critical() и tn_sys_exit_critical() может быть несимметричным и вложенным. Например, допустимо следующее:
void foo (void) { tn_sys_enter_critical(); /* . */ } void TN_TASK task_1 (void *param) { for (;;) { /* . */ tn_sys_enter_critical(); foo(); tn_sys_exit_critical(); /* . */ } }
Однако рекомендуется использовать симметричный вызов функций запрещения и разрешения планирования, так как обратное может привести к логическим ошибкам.
Запрещение переключения контекста не приветствуется, это является вмешательством в работу планировщика. Однако функции tn_sys_enter_critical() и tn_sys_exit_critical() могут быть полезны для выполнения относительно быстрых операций, для которых использование мютекса слишком избыточно. Примером такой операции может служить вывод в порт контроллера.
Системное время
Под системным временем подразумевается беззнаковая целая переменная, инкрементируемая каждый системный тик. Значение переменной может быть получено путем вызова функции tn_sys_time_get() и установлено с помощью вызова tn_sys_time_set() .
Системное время можно использовать для подсчета времени выполнения какого-либо действия (с точностью плюс-минус системный тик) или для «подстройки» периода «вызова» задач.
Допустим в системе присутствует задача, период «вызова» которой должен быть строго постоянным. Обычно такое поведение реализуется следующим образом.
void TN_TASK task_1 (void *param) { for (;;) { foo(); tn_task_sleep(FOO_PERIOD); } }
Возможна ситуация, когда длительность выполнения функции foo() может превышать системный тик — в этом случае периодичность нарушается. Способов решения такой проблемы несколько — например, дополнительная задача, освобождающая семафор с фиксированным периодом или использование объекта типа «таймер». Однако первое ведет к дополнительной трате ресурсов, а второе в TNKernel пока не реализовано.
Используя системное время проблему можно решить следующим образом:
void TN_TASK task_1 (void *param) { TN_SYS_TIM_T t; for (;;) { t = tn_sys_time_get(); foo(); t = tn_sys_time_get() - t; if (t FOO_PERIOD) tn_task_sleep(FOO_PERIOD - t); else tn_task_sleep(1); } }
Системные сервисы
TNKernel имеет следующий набор системных сервисов:
Сервис | Описание | Свойства |
---|---|---|
Основные сервисы | ||
tn_start_system() | Запуск системы | до начала работы системы |
tn_tick_int_processing() | Обслуживание системного таймера | |
tn_sys_tslice_ticks() | Управление round-robin планированием | |
tn_sys_context_get() | Получение текущего контекста системы | |
Запрещение переключения контекста | ||
tn_sys_enter_critical() | Вход в критическую секцию | |
tn_sys_exit_critical() | Выход из критической секции | |
Системное время | ||
tn_sys_time_get() | Получение системного времени | |
tn_sys_time_set() | Установка системного времени |