Arduino STM32 — таймеры
Это третья часть посвящённая плате Blue Pill (Arduino STM32).
Первая часть — прошивка
Вторая часть — внешние прерывания
Таймер — это адски крутая штуковина просто счетчик, который при достижении заданного значения может вызывать определённые события — прерывания, измерение времени между импульсами, генерировать ШИМ, отвечает за работу интерфейсов типа I2C, и ещё кучу всяких полезных делишек может делать.
Микроконтроллер STM32F103х имеет на борту четыре 16-ти битных таймера
• три таймера — TIM2, TIM3, TIM4, общего назначения (general-purpose timers).
• один продвинутый таймер — TIM1, с расширенными возможностями (advanced-control timers).
А так же…
• два WDT (WatchDog Timer).
• один SysTick Timer.
16-ти битный таймер умеет считать в диапазоне от 0 до 65535 (это значение называется «переполнение» см. ниже). То есть, говоря простым языком, в пямяти есть переменная, которая увеличивается на единицу с каждым следующим «тиком» таймера. При достижении заданного пользователем значения (если значение не задавать, то счёт идет до максимального значения — 65535), после чего счётчик сбрасывается в ноль, генерируется какое-либо событие, и отсчёт начинается заново.
Таймеры независимы друг от друга.
Таймеры тактируются (то есть «тикают») от системной частоты и соответственно не потребляют ресурсов.
Если не использовать дополнительных настроек, то таймер будет считать со скоростью 72Мгц (72 миллиона «тиков» в секунду). То есть до максимального значения (65535) он досчитает за ~0.9 миллисекунды. В общем шустрый парнишка.
Настройки таймера (режимы)
Так как настроек и режимов достаточно много, то я буду описывать их «от простого к сложному». Каким образом и при каких обстоятельствах применять те или иные режимы решать Вам.
На всякий случай:
мс — миллисекунда (1000мс = 1сек).
мкс — микросекунда (1000000мкс = 1сек).
нс — наносекунда (1000000000мкс = 1сек)
Предделитель
Как написано выше, если запустить таймер без настроек, то он будет считать очень быстро, что конечно же не всегда целесообразно. Для того чтобы понизить скорость отсчёта, нужно использовать предделитель.
Предделитель делит системную частоту на любое число от 1 до 65536. Например, если установить значение предделителя 3600, то таймер будет «тикать» со скоростью 20000 раз в секунду (72МГц / 3600 = 20КГц). То есть он будет доходить до максимального значения (65536) примерно за 3.22 секунды.
Для примера используем таймер №3, установим делитель 720 (72МГц / 720 = 100КГц) и активируем прерывание, которое будет происходить каждый раз когда таймер досчитает до мах. значения (примерно каждые 650мс). В обработчике будем менять состояние светодиода на противоположное…
volatile bool LEDOn13 = 0; void setup() { pinMode(LED_BUILTIN, OUTPUT); // PC13 Timer3.pause(); // останавливаем таймер перед настройкой Timer3.setPrescaleFactor(720); // устанавливаем делитель Timer3.attachInterrupt(TIMER_UPDATE_INTERRUPT, func_tim_3); // активируем прерывание Timer3.refresh(); // обнулить таймер Timer3.resume(); // запускаем таймер } void loop() {} void func_tim_3() // обработчик прерывания { digitalWrite(LED_BUILTIN, (LEDOn13 = !LEDOn13)); }
TIMER_UPDATE_INTERRUPT — режим таймера (автоматическое обновление и вызов прерывания).
Функции pause(), refresh() и resume() не обязательны, я их просто обозначил. Эти функции можно использовать в любом месте программы, например, если где-то в основном цикле нужно приостановить, запустить или обнулить таймер.
Переполнение
Переполнением называется то самое значение (65535), до которого считает таймер. Его можно менять по своему усмотрению.
Если переполнение не указано в коде, как в примере выше, то по умолчанию устанавливается мах. значение — 65535.
Если мы укажем в нашем коде переполнение равное 16000, тогда таймер будет обнуляться и начинать отсчёт заново достигнув этой цифры. Таким образом светодиод будет моргать в четыре раза чаще…
volatile bool LEDOn13 = 0; void setup() { pinMode(LED_BUILTIN, OUTPUT); // PC13 Timer3.pause(); // останавливаем таймер перед настройкой Timer3.setPrescaleFactor(720); // устанавливаем делитель Timer3.setOverflow(16000); // переполнение Timer3.attachInterrupt(TIMER_UPDATE_INTERRUPT, func_tim_3); // активируем прерывание Timer3.refresh(); // обнулить таймер Timer3.resume(); // запускаем таймер } void loop() {} void func_tim_3() // обработчик прерывания { digitalWrite(LED_BUILTIN, (LEDOn13 = !LEDOn13)); }
Оперируя предделителем и переполнением можно получать различные интервалы времени. Однако если нет желания заморачиваться с подсчётами, то можно функции setPrescaleFactor() и setOverflow() заменить одной функцией — setPeriod(), которая сделает всё сама, ей нужно только указать время в микросекундах…
volatile bool LEDOn13 = 0; void setup() { pinMode(LED_BUILTIN, OUTPUT); // PC13 Timer3.pause(); // останавливаем таймер перед настройкой Timer3.setPeriod(500000); // время в микросекундах (500мс) Timer3.attachInterrupt(TIMER_UPDATE_INTERRUPT, func_tim_3); // активируем прерывание Timer3.refresh(); // обнулить таймер Timer3.resume(); // запускаем таймер } void loop() {} void func_tim_3() // обработчик прерывания { digitalWrite(LED_BUILTIN, (LEDOn13 = !LEDOn13)); }
Таймер будет срабатывать каждые 500мс.
Эту функцию стоило бы описать самой первой, так как она самая простая, но тогда было бы непонятно, что она объединяет в себе настройки предделителя и переполнения.
Максимальная задержка ~59сек — setPeriod(59000000).
Вернёмся ко второму примеру и добавим функцию — setCount(). Эта функция в некотором роде противоположность setOverflow(), она указывает таймеру с какого места нужно начинать отсчёт.
Допустим что переполнение у нас равно 60000, то есть счётчик считает от 0 до 60000. Теперь если в обработчике прерывания дописать — Timer3.setCount(59000), то отсчёт будет вестись не от нуля, а от 59000. Соответственно таймер переполнится очень быстро.
Функция setCount() устанавливает новое значение только один раз, при следующей итерации таймер снова будет отсчитывать от нуля, поэтому её нужно вызывать каждый раз…
volatile int i = 0; volatile bool LEDOn13 = 0; void setup() { pinMode(LED_BUILTIN, OUTPUT); // PC13 Timer3.pause(); // останавливаем таймер перед настройкой Timer3.setPrescaleFactor(1720); // устанавливаем делитель Timer3.setOverflow(60000); // переполнение Timer3.attachInterrupt(TIMER_UPDATE_INTERRUPT, func_tim_3); // активируем прерывание Timer3.refresh(); // обнулить таймер Timer3.resume(); // запускаем таймер } void loop() {} void func_tim_3() // обработчик прерывания { digitalWrite(LED_BUILTIN, (LEDOn13 = !LEDOn13)); i++; if(i > 6 && i < 91) Timer3.setCount(59000); if(i > 90 && i < 101) Timer3.setCount(35000); if(i > 100) Timer3.setCount(0); }
Сначала светик мигнёт три раза с частотой заданой в setup(), потом заморгает очень быстро, потом медленней, и на конец вернётся к изначальной частоте.
Теперь пришло время разобраться с каналами таймеров…
У таймеров TIM1, TIM2, TIM3, TIM4 имеется в наличии по четыре канала ввода/вывода (TIMER_CH1…TIMER_CH4). Каналы могут работать в режимах — захват, сравнение, генерировать ШИМ и одиночные импульсы.
Сравнение
Работа каналов в режиме сравнения, это когда в специальную ячейку (для каждого канала своя ячейка) памяти помещается число от 0 до 65535, а таймер ведя отсчёт постоянно сравнивает своё значение со значением в ячейке. Как только значения совпадают, то тут же вызывается какое-либо событие, например прерывание. Таймер при этом продолжает считать пока не переполнится, после чего цикл повторяется.
Таким образом, один таймер может выполнить несколько действий с разными интервалами времени в рамках одного цикла переполнения.
В примере ниже происходит следующее: таймер №3 будет считать от 0 до 14000 (переполнение), и после каждого «тика» сравнивать своё значение со значениями сравнений каналов (в примере я задействовал все четыре канала). При каждом совпадении значений будут генерироваться прерывания…
пример
… при этом ничто не мешает использовать ещё и прерывание по переполнению.
пример
Поскольку в рамках одного цикла интервал получается очень маленьким, то можно просто добавить переменную, которая будет увеличиваться при каждом прерывании и событие произойдёт по достижении нужного значения…
пример
TIMER_CH1…TIMER_CH4 — номера каналов.
TIMER_OUTPUT_COMPARE — режим канала.
ШИМ
К каждому каналу таймера привязана конкретная «ножка» МК, которую можно использовать в различных целях, например генерировать сигнал ШИМ…
каналы
Для примера воспользуемся первым и вторым каналом третьего таймера. Переполнением (setOverflow) зададим период, а сравнением (setCompare) будем регулировать длину импульса…
t — длина импульса.
T — период.
void setup() { pinMode(PA6, PWM); // выход ШИМ канал 1 pinMode(PA7, PWM); // выход ШИМ канал 2 Timer3.setPrescaleFactor(72); // 1мкс Timer3.setOverflow(5000); // период 5 мс Timer3.setCompare(TIMER_CH1, 4000); // импульс 4 мс Timer3.setCompare(TIMER_CH2, 100); // импульс 0.1 мс Timer3.refresh(); // обнулить счётчик Timer3.resume(); } void loop() { Timer3.setCompare(TIMER_CH1, 4000); Timer3.setCompare(TIMER_CH2, 100); Timer3.refresh(); delay(2000); Timer3.setCompare(TIMER_CH1, 2000); Timer3.setCompare(TIMER_CH2, 700); Timer3.refresh(); delay(2000); Timer3.setCompare(TIMER_CH1, 700); Timer3.setCompare(TIMER_CH2, 2000); Timer3.refresh(); delay(2000); Timer3.setCompare(TIMER_CH1, 100); Timer3.setCompare(TIMER_CH2, 4000); Timer3.refresh(); delay(2000); }
Добавив ещё один канал можно рулить RGB-лентой…
RGB
Задействовав все четыре таймера можно подключить четыре ленты, и при этом выполнять ещё какие-либо действия без ущерба для всей этой иллюминации.
В примере под спойлером появилась новая функция — getCompare(). Если у некоторых функций заменить приставку — set на get, то можно посмотреть текущие значения…
volatile bool LEDOn13 = 0; void setup() { Serial.begin(115200); pinMode(LED_BUILTIN, OUTPUT); Timer3.setMode(TIMER_CH1, TIMER_OUTPUT_COMPARE); Timer3.pause(); Timer3.setPrescaleFactor(7200); Timer3.setOverflow(14000); Timer3.setCompare(TIMER_CH1, 3000); Timer3.attachInterrupt(TIMER_CH1, func_1); Timer3.resume(); } void loop() { Serial.println(Timer3.getPrescaleFactor()); Serial.println(Timer3.getOverflow()); Serial.println(Timer3.getCount()); // текущее значение счётчика Serial.println(Timer3.getCompare(TIMER_CH1)); Serial.println("
Sorry, the comment form is closed at this time.