Программный многозадачный таймер на STM32.

После начало освоения STM32 с применением библиотеки HAL и STM32Cube и наигравшись с LEDBlink пришло время прокачатся до уровня 1, благо экспириенса получил прилично, спасибо за это СООБЩЕСТВУ. Глаз положил решил написать модуль для программных таймеров на базе SysTick, но без особых проблем под эту задачу можно использовать любой другой аппаратный таймер.
И тут многие скажут, зачем изобретать велосипед? Почему не использовать уже обкатанные варианты?
Все эти варианты хороши, выбирай на вкус, но все таки я попробую изобрести свой велосипед в этой области, к тому же это:
  1. бесценный опыт
  2. попробовать реализовать другой подход к решению задачи
  3. удовлетворить свое самолюбие
  4. ну и т.д.

Итак, мы приступаем…

Не много предыстории...

Как истинный разработчик АСУ ТП я часто программирую ПЛК различных фирм. Для любого ПЛК в среде разработки заготовлены библиотеки для программных таймеров. Почти все они имеют однотипную функциональность. Использование таймеров в программе ПЛК требуется во многих родах задачах, все их описывать смысла нет, поэтому я покажу пару примеров из АСУ ТП и эти примеры «грубо портируем» в данный модуль.

Таймер с задержкой на включение.

Суть его сводится к тому, что при наличии разрешающего (запускающего) сигнала, в основном высокий уровень на входе, он начинает отсчет времени и по окончании оного устанавливает событие, то бишь логический выход, в единицу. Как только разрешающий сигнал пропал, то и выход таймера падает в ноль. Кстати, такой таймер считает только когда на всем протяжении отсчета присутствует разрешающий сигнал. ПРИМЕР: Нажали на кнопку и если удерживать её в течении определенного времени, то сработает выход на запуск какого либо механизма. Зачем это нужно: — реализация реле времени на ПЛК или фильтрация ложного срабатывания датчика, который расположен в неблагоприятных условиях, ну и т.д.
Временная диаграмма такого таймера представлена на рисунке.
Временная диаграмма таймера на включение

Таймер с задержкой на выключение.

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

Циклический таймер.

Здесь всё просто и понятно. Есть импульс запускающего сигнала, таймер начинает тикать задержку, по истечении устанавливает выход и статус в единицу, перезагружается и до бесконечности… Пока мы его не остановим или не удалим. Статусный флаг сбрасывается после опроса, а выход таймера упадет в ноль при следующей итерации аппаратного таймера.
Временная диаграмма расположилась далее.
Временная диаграмма циклического таймера

Одиночный таймер.
Логика циклического, только после окончание счета, он сам себя останавливает и ждет следующего запускающего импульса. Остановить счет таймера можно и раньше, принудительно выключив или удалив.
Диаграмму наблюдаем здесь.
Временная диаграмма одиночного таймера

Модуль SwTimer.

Название модуля очень оригинальное говорит само за себя. Модуль состоит из двух файлов: хидер и сорц. Тоже довольно оригинальное решение банально.
Рассмотрим хидер:

#ifndef SW_TIMER_H_
#define SW_TIMER_H_

#define SwTimerCount  64	//Количество программных таймеров

/*Режимы работы таймеров*/
typedef enum
	{
		SWTIMER_MODE_EMPTY,
		SWTIMER_MODE_WAIT_ON,
		SWTIMER_MODE_WAIT_OFF,
		SWTIMER_MODE_CYCLE,
		SWTIMER_MODE_SINGLE
} SwTimerMode;


/*Стурктура программного таймера*/
typedef struct
    {
	unsigned LocalCount:32;	//Переменная для отсчета таймера
    	unsigned Count:24;		//Переменная для хранения задержки
    	unsigned Mode:3;		//Режим работы
    	unsigned On:1;			//Разрешиющий бит
    	unsigned Reset:1;		//Сброс счета таймера без его выключения
    	unsigned Off:1;			//Останавливающий бит
    	unsigned Out:1;			//Выход таймера
    	unsigned Status:1;		//Состояние таймера
}SW_TIMER;

#if (SwTimerCount>0)
volatile SW_TIMER TIM[SwTimerCount]; //Объявление софтовых таймеров
#endif


void SwTimerWork(volatile SW_TIMER* TIM, unsigned char Count);	//Сама функция для обработки таймеров
void OnSwTimer(volatile SW_TIMER* TIM, SwTimerMode Mode, unsigned int SwCount);	//Подготовливает выбранный из массива таймер
unsigned char GetStatusSwTimer(volatile SW_TIMER* TIM);	//Считывание статуса таймера
#endif /* SW_TIMER_H_ */


Тут все должно быть понятно. Единственное на что, хотелось бы обратить внимание, то что сам таймер является 24-х битным. В данной структуре это позволяет программному таймеру занимать место в 8 байт. 24 бита при переполнении аппаратного таймера в 1 мс позволяет достичь задержки в 4,66 часа или 16 777 секунд. Вполне достаточно.

Функций не много. Главная:
void SwTimerWork(volatile SW_TIMER* TIM, unsigned char Count);

Обеспечивает обработку массива таймеров.

void OnSwTimer(volatile SW_TIMER* TIM, SwTimerMode Mode, unsigned int SwCount);

Подготавливает таймер из массива, устанавливает задержку, режим.

unsigned char GetStatusSwTimer(volatile SW_TIMER* TIM);

Считывает состояние таймера. -1 если выбранный таймер пустой.

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

Вызов модуля.

Данный модуль можно применять как с теми функциями, что объявлены в хидере, так и непосредственно устанавливая значения прямо в структуре таймера.
Вызов основной функции модуля может происходить в двух вариациях:
  1. Непосредственно в обработчике прерывания от аппаратного таймера
  2. В майне по флагу из обработчика прерывания
Кому как удобней.
Вот пример из обработчика прерывания:

/* USER CODE BEGIN 0 */
#include "sw_timer.h"
/* USER CODE END 0 */

void SysTick_Handler(void)
{
  /* USER CODE BEGIN SysTick_IRQn 0 */

  /* USER CODE END SysTick_IRQn 0 */
  HAL_IncTick();
  HAL_SYSTICK_IRQHandler();

  /* USER CODE BEGIN SysTick_IRQn 1 */
  SwTimerWork(TIM,SwTimerCount);
  /* USER CODE END SysTick_IRQn 1 */
}



Или флагом:


/* USER CODE BEGIN 0 */
extern uint8_t FlagSwTimer;
/* USER CODE END 0 */

void SysTick_Handler(void)
{
  /* USER CODE BEGIN SysTick_IRQn 0 */

  /* USER CODE END SysTick_IRQn 0 */
  HAL_IncTick();
  HAL_SYSTICK_IRQHandler();

  /* USER CODE BEGIN SysTick_IRQn 1 */
  FlagSwTimer=1;
  /* USER CODE END SysTick_IRQn 1 */
}

/*В майне*/
while(1){
	if (FlagSwTimer){
		#if (SwTimerCount>0)

			//Обработчик программных таймеров из sw_timer.c
			SwTimerWork(TIM,SwTimerCount);

		#endif
        FlagSwTimer=0;
}


Применение.

Инициализируем четыре первых таймера из массива.



#define I_DI_READ_0() HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) //Считывание кнопки

        OnSwTimer(&TIM[0],SWTIMER_MODE_WAIT_OFF,1000);
	OnSwTimer(&TIM[1],SWTIMER_MODE_SINGLE,1000);
	OnSwTimer(&TIM[2],SWTIMER_MODE_WAIT_ON,1000);
	OnSwTimer(&TIM[3],SWTIMER_MODE_CYCLE,1000);
        TIM[3].Off=0;    
        TIM[3].On=1;    //Запустили циклический таймер
        TIM[1].Off=0;
        TIM[1].On=1;    //Запустили одиночный таймер


А дальше в программе запускаем, останавливаем, перегружаем, удаляем по ходу пьесы.




    while (1){
        TIM[2].On=I_DI_READ_0();    //Считываем кнопку, и пока нажата таймер отсчитывает время
        HAL_GPIO_WritePin(GPIOD,GPIO_PIN_15,TIM[2].Out);    //Как до тикал, зажгли светодиод
        TIM[0].On=I_DI_READ_0();    //Тот же фокус, но с таймером с задержкой на выключение
        HAL_GPIO_WritePin(GPIOD,GPIO_PIN_14,TIM[0].Out);    //Пока тикает, горит светодиод
        if (GetStatusSwTimer(&TIM[3])){
            HAL_GPIO_TogglePin(GPIOD,GPIO_PIN_13);    //Обрабатываем флаг циклического таймера, инвертируем выход
        }
        if (GetStatusSwTimer(&TIM[1])){
            HAL_GPIO_TogglePin(GPIOD,GPIO_PIN_12);    //Обрабатываем флаг одиночного таймера, инвертируем выход
        }
        TIM[3].Reset=I_DI_READ_0();    //Пока нажата кнопка, ресетим циклический таймер
}


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

    TIM[3].On=0;
    TIM[3].Off=1;

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

    if (GetStatusSwTimer(&TIM[1]))     //Можно так
    if (TIM[3].Status){                //Или так
        TIM[3].Status=0;                //Сбрасываем бит, так как он сбросится только после очередного вызова
                                        //основной функции обработки массива, то есть через переполнение аппаратного таймера
        /*UserCode*/
    }

Если таймер больше не нужен, то сократить время выполнения функции обработки массива таймеров можно если не просто остановить таймер, а удалить его, то есть перевести в режим ПУСТО. Для этого вызываем функцию подготовки таймера с режимом SWTIMER_MODE_EMPTY. Или прямо это указываем.

    OnSwTimer(&TIM[3],SWTIMER_MODE_EMPTY,0);    //Можно так
    TIM[3].Mode=SWTIMER_MODE_EMPTY;            //Или так


Не много разные по смыслу первые два таймера и вторые два объединенны в одну структуру, дабы не плодить лишних функций и т.д.

Данная статья является переработанной версией данного урока:STM32. Уроки по программированию STM32F4. Урок № 4. Программный многозадачный таймер STM32F4.

Видео демонстрирующие функции модуля программного многозадачного таймера:


Код пока не оптимизировался и не тестировался в полном объеме в плане багов, а был написан почти в лоб. Так что конструктивная критика всегда приветствуется. Может в ходе обсуждения найдуться более оригинальные решения или допилится данный метод до совершенства оптимального состояния.
Наверное на этом всё.
  • +4
  • 09 декабря 2015, 13:19
  • Helix
  • 1
Файлы в топике: sw_timer.zip

Комментарии (13)

RSS свернуть / развернуть
0
Два из них реализованы в данном модуле. Остальные типы представленные у Вас имеют узкую специализацию.
0
Чет STEP 7 запахло=))
0
kalik показал свою НеВнимательно
Не много предыстории…
Как истинный разработчик АСУ ТП я часто программирую ПЛК различных фирм.
Для любого ПЛК в среде разработки заготовлены библиотеки для программных таймеров. Почти все они имеют однотипную функциональность.
Использование таймеров в программе ПЛК требуется во многих родах задачах, все их описывать смысла нет, поэтому я покажу пару примеров из АСУ ТП и эти примеры «грубо портируем» в данный модуль.

Два из них реализованы в данном модуле. Остальные типы представленные у Вас имеют узкую специализацию.
Узкую/неузкую… но имеют применение
0
1. Я не являюсь автором этого поста!
2. Как Разработчик Я много чего программировал.
3. Что такое ПЛК, как чего и почем в них Я прекрасно знаю.
0
1. Я могу определить автора поста — по подписи в конце поста!
2. Как Пешеход Я пешком много где ходил.
3. Ну и чё — почём в них и чем там пахнет?
0
Пахнет горелыми оптопарами:


охренительным маркетингом, удешевлением всего что возможно и условно хреновой поддержкой.
Для определения камента выше по дереву пользуйтесь кнопочкой:
0
не суй руки куда не следует и оптроны не будут пахнуть
Что будет если вместо 24V подать на дискретный вход SM321 220 V?
пишу сразу всем читающим в одном сообщении с цитированием и ответом по смыслу, а не по дереву.
0
Для модуля программных таймеров на мк, наверное будет излишним и неправильным портировать все имеющиеся в арсенале сред разработок для ПЛК виды таймеров. В данном модуле реализовал, на мой взгляд, самые необходимые.
0
Файл сорца показывать не буду, там все прозрачно и кода получилось не много.
Сколько таймеров одновременно могут работать? И разнотипных? Арбитраж их одновременного срабатывания?
0
Количество таймеров устанавливается в дефайне в хидере. Режим таймера задается при его инициализации. При обработке массива таймеров в зависимости от его типа своя логика. Срабатывания фиксируется флагом статуса таймера которое может использоваться в любой момент основной программы. Выход активируется в зависимости от режима работы таймера и может использоваться также в основной программе. Два типа таймеров с задержкой на включение и выключение оперируют входом и выходом как основными функциями. Циклический таймер флагом статуса. Одиночный как флагом так и выходом. Прикрепил файлы модуля к топику.
0
Часть полей (или всю структуру) SW_TIMER лучше объявить как volatile иначе оптимизатор может сломать логику работы, например в выражениях типа

TIM[3].On=0;
TIM[3].Off=1;


while(TIM[3].Status == 0)


и т. д.
0
  • avatar
  • e_mc2
  • 09 декабря 2015, 18:47
Согласен. Спасибо.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.