Не отступай и не сдавайся !!!

+7(495)233-9079
arduino4home@gmail.com

Урок 11
Программные таймеры в Ардуино. Циклы с различными временами периода от одного таймера.

Предыдущий урок Ардуино - главная Следуюший урок

Научимся создавать программные таймеры  в параллельных процессах. Напишем учебную программу для охранной сигнализации.

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

Как создавать программные таймеры для таких циклов. В принципе, также как и аппаратные.

  • В системе циклически вызывается одно прерывание от аппаратного таймера. Время периода выбирается наименьшим среди требуемых программных таймеров.
  • В обработчике прерывания от таймера, в каждом цикле прибавляется 1 к счетчику программного таймера.
  • При достижении кодом счетчика заданного значения он сбрасывается в 0. Период программного таймера вычисляется, как  время периода прерывания аппаратного таймера умноженное на значение кода сброса счетчика.

Вот пример программы с тремя  программными таймерами на 10, 200 и 1000 мс.

#define CYCLE_1_TIME 5    //- время цикла 1 ( * 2 = 10 мс)
#define CYCLE_2_TIME 100  //- время цикла 2 ( * 2 = 200 мс)
#define CYCLE_3_TIME 500  //- время цикла 3 ( * 2 = 1 сек)

byte  timerCount1;    //- счетчик таймера 1
byte  timerCount2;    //- счетчик таймера 2
boolean flagTimer2;   //- признак программного таймера 2
unsigned int timerCount3; //- счетчик таймера 3

void loop() {

//- программный таймер 1, период 10 мс
  //- счетчик таймера контролируется в асинхронном цикле
  if ( timerCount1 >= CYCLE_1_TIME ) {
    timerCount1= 0; 
    //- код программы вызывается каждые 10 мс
  }

//- программный таймер 2, период 200 мс
  //- счетчик таймера контролируется в обработчике прерывания
  if ( flagTimer2 == true ) {
    flagTimer2= false; 
    //- код программы вызывается каждые 200 мс    
  }

}

// обработчик прерывания 2 мс
void  timerInterupt() {

timerCount1++;  //- + 1 к счетчику таймера 1

timerCount2++;  //- + 1 к счетчику таймера 2
  if ( timerCount2 >= CYCLE_2_TIME ) {
    timerCount2= 0;     //- сброс счетчика
    flagTimer2= true;   //- установка флага таймера 2
  }

//- программный таймер 3, период 1000 мс
  //- счетчик таймера контролируется в обработчике прерывания
  //- выполняется также в цикле прерывания   
  timerCount3++;  //- + 1 к счетчику таймера 3
  if ( timerCount3 >= CYCLE_3_TIME ) {
    timerCount3= 0;     //- сброс счетчика
    //- код программы вызывается каждые 1000 мс    
  }  
}

Для всех трех таймеров созданы свои счетчики. Ко всем счетчикам в обработчике прерывания каждые 2 мс прибавляется 1.

Счетчик таймера 1 проверяется в асинхронном цикле loop(). Когда его код достигает значения равного константе CYCLE_1_TIME, он сбрасывается и выполняется код программы для этого цикла.

Счетчик второго таймера проверяется в обработчике прерывания. При достижении кода сброса он сбрасывается и вырабатывается признак flagTimer2. Этот признак анализируется в асинхронном цикле и, при его активном состоянии выполняется код для цикла 2 с периодом 200 мс.

Разница у этих способов заключается в том, что при контроле счетчика в асинхронном цикле его состояние должно проверятся не реже времени периода прерывания. Если, к примеру, в асинхронном цикле программа задержится на 10 мс, то за это время счетчик timerCount1 насчитает лишние 5 единиц и будет сброшен при большем значении. Таким образом, время цикла нарушится. Во втором случае счетчик таймера 2 будет сброшен в обработчике прерывания вовремя. При задержке в асинхронном цикле задержится реакция на программный таймер 2, но период самого цикла не нарушится.

Счетчик третьего таймера контролируется в обработчике прерывания и там же выполняется код программы для этого таймера.

Могут быть разные варианты реализации циклов с разными периодами. Бывают еще программные таймеры синхронизированные друг от друга или от общего таймера.  Какой способ применять надо решать в конкретной задаче.

Программа для упрощенной охранной сигнализации.

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

К плате Ардуино подключены следующие компоненты сигнализации:

  • Датчик открывания двери. Имитируем его кнопкой 1, считаем, что в нормальном состоянии (дверь закрыта) датчик разомкнут, а при открытой двери – замкнут.
  • Светодиод состояния сигнализации, расположенный над входной дверью:
    • отключена – не светится;
    • включена (режим охрана) – мигает раз в секунду;
    • сработала (режим тревога) – мигает 4 раза в секунду.
  • Кнопка включения/отключения сигнализации. Скрытая кнопка у входной двери. Реально это может быть замаскированный геркон, коммутацию которого можно производить постоянным магнитом.
  • Сирена тревоги –  пьезоэлектрический излучатель.

Схема подключения этих элементов к плате Ардуино.

У меня макет устройства выглядит так.

Алгоритм работы устройства.

  • При включении сигнализация отключена, светодиод состояния не светится.
  • Закрыв входную дверь, нажимаем скрытую кнопку (или прикасаемся магнитом к скрытому геркону). Сигнализация включается. О чем сигнализирует светодиод, мигая раз в секунду. В этом режиме сигнализация контролирует состояние датчика двери.
  • Перед тем как открыть дверь надо отключить сигнализацию нажатием скрытой кнопки.
  • Если дверь откроется при включенной сигнализации, устройство перейдет в режим тревоги. Начнет звучать сигнал сирены, и светодиод будет мигать с частотой 4 раза в секунду.
  • В этом состоянии устройство будет находиться в течение 30 секунд, а затем отключится. Можно отключить сигнал тревоги раньше нажатием кнопки.

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

Последовательность разработки программы.

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

  • Создаем объекты кнопка (Button):
    • doorSens – датчик двери;
    • secretButton – скрытая кнопка.
  • Создаем прерывание от таймера с периодом 2 мс.
  • Вызываем в прерывании методы фильтрации сигналов датчика двери и кнопки.
  • Назначаем выводы контроллера для всех компонентов.
  • Задаем режим выводов для светодиода и сирены.

// упрощенная охранная сигнализация

#include <MsTimer2.h>
#include <Button.h>

#define DOOR_SENS_PIN 12      //- датчик двери подключен к выводу 12
#define SECRET_BUTTON_PIN 11  //- скрытая кнопка подключена к выводу 11
#define LED_PIN 10            //- светодиод подключен к выводу 10
#define SIREN_PIN 9           //- сирена подключена к выводу 9

Button doorSens(DOOR_SENS_PIN, 50);  //- создание объекта датчик двери, типа кнопка
Button secretButton(SECRET_BUTTON_PIN, 25); //- создание объекта скрытая кнопка, типа кнопка

void setup() {
  pinMode(LED_PIN, OUTPUT);      //- определяем вывод светодиода как выход
  pinMode(SIREN_PIN, OUTPUT);      //- определяем вывод сирены как выход
  MsTimer2::set(2, timerInterupt); //- задаем период прерывания по таймеру 2 мс 
  MsTimer2::start();              //- разрешаем прерывание по таймеру
}

void loop() {
//- пока кода нет
}

// обработчик прерывания 2 мс
void  timerInterupt() {

doorSens.filterAvarage();  //- вызов метода фильтрации сигнала для датчика двери  
  secretButton.filterAvarage();  //- вызов метода фильтрации сигнала для скрытой кнопки 

}

Теперь у нас в программе есть все компоненты системы. На этом этапе можно проверить аппаратную часть устройства. Для этого надо временно сделать в цикле loop() простую логическую связь между кнопками, светодиодом и сиреной. И убедится, что на открытие двери или нажатие кнопки реагирует светодиод. Мы так делали в предыдущих уроках.

Если не проверяем аппаратную часть, то, по крайней мере, компилируем программу для проверки на формальные ошибки.

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

boolean sirenOn;  //- признак включения сирены

В обработчике прерывания напишем:

// блок управления сиреной
  if ( sirenOn == true) digitalWrite(SIREN_PIN, ! digitalRead(SIREN_PIN));

Для проверки можно вставить в loop()

sirenOn= secretButton.flagPress;  //- проверка сирены

При нажатии скрытой кнопки сирена (пьезоизлучатель) будет издавать тревожный сигнал. Не забудьте удалить после проверки.

Структура программы.

Задумываемся о структуре и режимах программы. Самая простая и логичная структура следующая. В асинхронном цикле выделены блоки – режимы. В каждом блоке программа работает по циклу, а на другие блоки (режимы) переходит при определенных условиях с помощью оператора goto. Логически можно выделить следующие режимы работы устройства:

  • сигнализация отключена (ОТКЛЮЧЕНА);
  • сигнализация включена (ОХРАНА);
  • сигнализация сработала (ТРЕВОГА).

Давайте создадим такие программные блоки, каждый с бесконечным циклом и меткой вначале для перехода.

void loop() {

//---------------------- режим ОТКЛЮЧЕНА --------------------------
guard_off:
  while (true)  {

  }

//---------------------- режим ОХРАНА --------------------------------
guard_on:
  while (true)  {

  }

//---------------------- режим ТРЕВОГА --------------------------------
alarm:
  while (true)  {

  }            
}

Считается, что применение оператора goto надо ограничивать. Но в случаях, когда этот оператор упрощает понимание логики программы, облегчает ее читаемость, применение goto признано оправданным. У нас как раз тот случай. Ведь по логике может быть переход в любой блок из любого. Операторами break, continue и т.п. такие переходы не обеспечить.

Заполняем кодом каждый блок-режим.

Для блока ОТКЛЮЧЕНА:

  • светодиод не горит;
  • сирена не звучит;
  • если нажали кнопку, то переход на режим ОХРАНА.

//---------------------- режим ОТКЛЮЧЕНА --------------------------------
guard_off:
  while (true)  {

digitalWrite(LED_PIN, LOW); //- светодиод не горит
    sirenOn= false;           //- сирена не звучит

//- если нажали кнопку, переход на режим ОХРАНА
    if ( secretButton.flagClick == true ) {
      secretButton.flagClick= false;
      goto  guard_on;      
    }    
  }

Можно попробовать загрузить в плату. Светодиод не светится, сирена не звучит.

Для блока ОХРАНА:

  • светодиод мигает раз в секунду;
  • сирена не звучит;
  • если нажали кнопку, то переход на режим ОТКЛЮЧЕНА;
  • если сработал датчик двери, то переход на режим ТРЕВОГА.

Чтобы реализовать мигание светодиода объявляем счетчик времени ledTimeCount и две константы: TIME_LED_PERIOD и TIME_LED_ON. Первая задает период мигания светодиода (500 для 1 секунды), вторая  время включенного состояния (100 для 0,2 сек). Счетчик времени увеличиваем на 1 в каждые 2 мс в обработчике прерывания, а светодиодом управляем простой логикой в блоке режима ОХРАНА.

#define TIME_LED_PERIOD 500   //- время периода мигания светодиода (* 2 мс)
#define TIME_LED_ON 100       //- время включенного светодиода

unsigned int ledTimeCount; //- счетчик времени для светодиода

//---------------------- режим ОХРАНА --------------------------------
guard_on:
  while (true)  {

sirenOn= false;   //- сирена не звучит
    alarmTimeCount= 0;  //- сброс счетчика времени тревоги      

    //- светодиод мигает раз в секунду
    if ( ledTimeCount >= TIME_LED_PERIOD )  ledTimeCount= 0;
    if ( ledTimeCount < TIME_LED_ON )  digitalWrite(LED_PIN, HIGH);
    else digitalWrite(LED_PIN, LOW);

//- если нажали кнопку, переход на режим ОТКЛЮЧЕНА
    if ( secretButton.flagClick == true ) {
      secretButton.flagClick= false;
      goto  guard_off;      
    }    

    //- если сработал датчик двери, переход на режим ТРЕВОГА
    if ( doorSens.flagPress == true ) goto  alarm;

  }

//------------------- обработчик прерывания 2 мс ---------------------
void  timerInterupt() {

// ………….

ledTimeCount++;  //- счетчик времени мигания светодиода    
}

Загружаем в плату Ардуино. Проверяем, что нажатие кнопки переводит устройство в режим ОХРАНА (мигает светодиод). Следующее нажатие кнопки возвращает в режим ОТКЛЮЧЕНА (светодиод погашен).

Остается заполнить блок режима ТРЕВОГА:

  • светодиод мигает 4 раза в секунду;
  • звучит сирена;
  • если нажали кнопку, то переход на режим ОТКЛЮЧЕНА;
  • если прошло 30 секунд – переход на режим ОТКЛЮЧЕНА.

Добавляем константу TIME_LED_ALARM - время периода мигания светодиода при ТРЕВОГЕ. Используем тот же счетчик для светодиода.

Для отсчета времени в режиме тревоги объявляем счетчик alarmTimeCount и константу TIME_ALARM - время в режиме ТРЕВОГА (30 сек). Перед входом в режим ТРЕВОГА счетчик должен быть сброшен.

Все. Программа готова. Итоговый скетч:

// упрощенная охранная сигнализация

#include <MsTimer2.h>
#include <Button.h>

#define DOOR_SENS_PIN 12      //- датчик двери подключен к выводу 12
#define SECRET_BUTTON_PIN 11  //- скрытая кнопка подключена к выводу 11
#define LED_PIN 10            //- светодиод подключен к выводу 10
#define SIREN_PIN 9           //- сирена подключена к выводу 9

#define TIME_LED_PERIOD 500   //- время периода мигания светодиода (* 2 мс)
#define TIME_LED_ON 100       //- время включенного светодиода
#define TIME_LED_ALARM 62   //- время периода мигания светодиода при ТРЕВОГЕ (* 2 мс)
#define TIME_ALARM 15000   //- время в режиме ТРЕВОГА (* 2 мс)

Button doorSens(DOOR_SENS_PIN, 50);  //- создание объекта датчик двери, типа кнопка
Button secretButton(SECRET_BUTTON_PIN, 25); //- создание объекта скрытая кнопка, типа кнопка

boolean sirenOn;  //- признак включения сирены
unsigned int ledTimeCount; //- счетчик времени для светодиода
unsigned int alarmTimeCount; //- счетчик времени тревоги

void setup() {
  pinMode(LED_PIN, OUTPUT);      //- определяем вывод светодиода как выход
  pinMode(SIREN_PIN, OUTPUT);      //- определяем вывод сирены как выход
  MsTimer2::set(2, timerInterupt); //- задаем период прерывания по таймеру 2 мс 
  MsTimer2::start();              //- разрешаем прерывание по таймеру
}

void loop() {

//---------------------- режим ОТКЛЮЧЕНА --------------------------
guard_off:
  while (true)  {

digitalWrite(LED_PIN, LOW); //- светодиод не горит
    sirenOn= false;           //- сирена не звучит

//- если нажали кнопку, переход на режим ОХРАНА
    if ( secretButton.flagClick == true ) {
      secretButton.flagClick= false;
      goto  guard_on;      
    }    
  }

//---------------------- режим ОХРАНА --------------------------------
guard_on:
  while (true)  {

sirenOn= false;   //- сирена не звучит
    alarmTimeCount= 0;  //- сброс счетчика времени тревоги      

    //- светодиод мигает раз в секунду
    if ( ledTimeCount >= TIME_LED_PERIOD )  ledTimeCount= 0;
    if ( ledTimeCount < TIME_LED_ON )  digitalWrite(LED_PIN, HIGH);
    else digitalWrite(LED_PIN, LOW);

//- если нажали кнопку, переход на режим ОТКЛЮЧЕНА
    if ( secretButton.flagClick == true ) {
      secretButton.flagClick= false;
      goto  guard_off;      
    }    

    //- если сработал датчик двери, переход на режим ТРЕВОГА
    if ( doorSens.flagPress == true ) goto  alarm;

  }

//---------------------- режим ТРЕВОГА --------------------------------
alarm:
  while (true)  {

sirenOn= true;   //- звучит сирена

//- светодиод мигает 4 раза в секунду
    if ( ledTimeCount >= TIME_LED_ALARM ) {
      ledTimeCount= 0;
      digitalWrite(LED_PIN, ! digitalRead(LED_PIN));
    }

//- если нажали кнопку, переход на режим ОТКЛЮЧЕНА
    if ( secretButton.flagClick == true ) {
      secretButton.flagClick= false;
      goto  guard_off;      
    }   

//- проверка времени тревоги ( 30 сек )
    if ( alarmTimeCount >= TIME_ALARM ) goto  guard_off;

  }            
}

//------------------- обработчик прерывания 2 мс ---------------------
void  timerInterupt() {

doorSens.filterAvarage();  //- вызов метода фильтрации сигнала для датчика двери  
  secretButton.filterAvarage();  //- вызов метода фильтрации сигнала для скрытой кнопки 

//- блок управления сиреной
  if ( sirenOn == true) digitalWrite(SIREN_PIN, ! digitalRead(SIREN_PIN));

ledTimeCount++;  //- счетчик времени мигания светодиода  
 alarmTimeCount++;  //- счетчик времени тревоги

}

Скетч программы с расширением .ino можно загрузить по этой ссылке.

Загружаем в плату, проверяем. Открывание двери имитируем нажатием первой кнопки. Все работает.

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

Следующий урок будет посвящен передаче данных по последовательному интерфейсу UART и отладке программ на Ардуино.


Хостинг нашего сайта осуществляется узлом www.cherepovets-city.ru
© 2000-2018 23/8/18 21:46->