Простой программный таймер для конечных автоматов
Достался мне тут на доделку один проект. Точнее два, но от одного автора. Управление промышленным оборудованием.
Сам проект ничего особого, простая логика на конечных автоматах. Но мне понравился там как реализован таймер. Я обычно предпочитаю динамический таймер, а идею глобального времени только высказывал, но так и не применил, т.к. в основном все делал на диспетчере или RTOS и там этот подход не особо удобен. Но если логика построена на простом суперцикле с набором функций-автоматов в main цикле, то такая реализация таймера фактически стала классикой. Вот, пользуясь случаем, заполняю этот пробел. Архитектура тут не важна. Главное чтоб был таймер способный давать прерывание раз в тик. Тик обычно 1мс.
▌Принцип работы и использование
У нас есть глобальная переменная TimeMs которая инкрементируется по прерыванию таймера раз в 1мс. Когда мы хотим поставить выдержку, то просто берем текущее значение TimeMs прибавляем к нему нашу выдержку и запоминаем все это в статичной переменной, пусть будет Delay, которая определена непосредственно в той функции автомата которая эту задержку использует. И при каждом следующем входе в автомат она будет проверять нет ли у нас условия Delay >= TimeMs.
То есть автомат мигалка будет выглядеть так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | void Blink (void) { // Переменные объявленные как static не исчезают после выхода из функции. А сохраняют свое состояние. static uint8_t blink_state = 0; // Переменная состояния конечного автомата static uint32_t Delay; // Переменная программного таймера мигалки, время моргания switch (blink_state) { case 0: // Первый вход, инициализация и первый поджиг. { Delay = MainTimerSet(1000); // Ставим задержку на 1000мс LED_ON(); // Зажигаем диодик blink_state = 1; // Переходим в следующее состояние автомата break; // Выход из состояния } case 1: // Первая стадия рабочего цикла (Не горим) { if ( !MainTimerIsExpired(Delay) ) break; // Если 1с не прошла - сразу выходим // Функция MainTimerIsExpired проверяет по // таймерной переменной TimeMs не // стал ли Delay меньше чем TimeMs LED_OFF(); // Если секунда прошла, то гасим диодик Delay = MainTimerSet(500); // Запоминаем время выключенной фазы 0.5с blink_state = 2; // Переключаем автомат во вторую стадию break; // Выход из состояния } case 2: // Вторая стадия рабочего цикла (Горим) { if ( !MainTimerIsExpired(Delay) ) break; // Если 0,5с не прошла - сразу выходим LED_ON(); // Если секунда прошла, то зажигаем диодик Delay = MainTimerSet(1000); // Запоминаем время включенной фазы 1с blink_state = 1; // Переключаем автомат в первую стадию break; // Выход из состояния } default: break } } |
Ну, а в самом Main вызов автоматов разных задач выглядит как то так:
1 2 3 4 5 6 7 8 9 10 | void main (void) { while (1) { KeyScan(); // Автомат отвечающий за сканирование клавы бла бла бла LCD_process(); // Автомат работающий с дисплеем бла бла бла Blink(); // Наша мигалка } |
Теперь немного о реализации самой библиотечки таймера.
(далее…)