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

Arduino UNO R3

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

Предыдущий урок     Список уроков     Следующий урок

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

 

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

  • В системе циклически вызывается одно прерывание от аппаратного таймера. Время периода выбирается наименьшим среди требуемых программных таймеров.
  • В обработчике прерывания от таймера, в каждом цикле прибавляется 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++;  // счетчик времени тревоги
 
}

Зарегистрируйтесь и оплатитеВсего 60 руб. в месяц за доступ ко всем ресурсам сайта!

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

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

 

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

 

Предыдущий урок     Список уроков     Следующий урок

4

Автор публикации

не в сети 1 день

Эдуард

280
Комментарии: 1936Публикации: 197Регистрация: 13-12-2015

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

  1. Эдуард, приветствую,
    Я сделал «учебную» сигнализацию по материалам вашего урока. Но т.к. я стараюсь не просто повторять код методом вставки, а писать самостоятельно, как запомнил задачу, а потом сверяться с вашей реализацией и проправлять свою, то окончательному результату предшестувует отладка. И здесь во время этой отладки я налетел на неприятный глюк. Я начал с того, что попытался, используя ранее написанный класс «кнопка» сделать два блока-режима «online» и «offline», где, условно, если девайс в онлайне, то светодиод горит, а если в оффлайне — не горит. Далее решил усложнять по мере отладки.
    Ранее написанная программа включения-выключения светодиода с классом-библиотекой работала прекрасно, а вот после разбиения на функциональные модули с «вечными» циклами в каждом, конструкция работать прекратила. Блоки выглядели так:
    //————begin code-fragment———
    offline:
    while(true) {
    if (button1.flagClick == HIGH) {
    ledState=HIGH;
    digitalWrite(LED_PIN,ledState);
    button1.flagClick=LOW;
    goto online;
    }
    }

    online:
    while(true) {
    if (button1.flagClick == HIGH) {
    ledState=LOW;
    digitalWrite(LED_PIN,ledState);
    button1.flagClick=LOW;
    goto offline;
    }
    }
    //—————end code-fragment————
    Обработчик прерывания по таймеру исправно отрабатывал, флаг нажатия выставлялся, но почему-то не отлавливался в основной программе.
    Я грешил на код основной программы, затем на класс, также хоть и по вашей указке, но написанный самостоятельно. После этого скачал вашу библиотеку, попробовал с ней — то же самое. После этого скачал и запустил ваш код сигнализации, который заработал и с моим и с вашим классом. В итоге выяснилось, что если вставить перед проверкой флага нажатия хотя бы один оператор, неважно какой — я пробовал delay, Serial.print или прописывать дополнительно состояние светодиода, то и мой код тоже начинает работать. Т.е. вот версия, которая работает:
    //—————begin code-fragment———————-

    offline:
    while(true) {
    delay(1); //если вместо этого поставить строку ниже, а эту убрать, то тоже будет работать
    //digitalWrite(LED_PIN,LOW);
    if (button1.flagClick == HIGH) {
    ledState=HIGH;
    digitalWrite(LED_PIN,ledState);
    button1.flagClick=LOW;
    goto online;
    }
    }

    online:
    while(true) {
    delay(1);
    //digitalWrite(LED_PIN,HIGH);
    if (button1.flagClick == HIGH) {
    ledState=LOW;
    digitalWrite(LED_PIN,ledState);
    button1.flagClick=LOW;
    goto offline;
    }
    }

    //——————end code-fragment——————

    В коде сигнализации до проверки условия нажатия тоже присутствуют какие-то действия, поэтому там такой проблемы, как была у меня изначально — нет.
    Я попробовал воспроизвести эту ситуацию на двух версиях плат — Micro и Mega2560. Поведение было одинаковым. Вы случайно ни на что подобное не наступали? Как можно отловить причину такого поведения?

    Прошу прощения за много букв, если сочтете лишним, не публикуйте этот коментарий, а ответьте лично в почту.

    Спасибо,
    Денис.

    0
    • Денис, здравствуйте!
      Я столкнулся с подобным эффектом, который объяснить не смог.
      В прерывании по таймеру у меня взводился флаг, а в бесконечном цикле while() он анализировался. Программа никак на него не реагировала. Я убрал все классы, оставил самые примитивные операции, но результат остался прежним.
      Далее я выяснил, что программа правильно реагировала на признак из обработчика прерывания только при выполнении цикла loop() до конца. Очевидно, что при каждом цикле loop происходит какая-то перегрузка переменных или что-то подобное. Интересно, что программ правильно работала с бесконечным циклом while() если в нем присутствовала любая системная функция, например digitalWrite(), delay(), да любая. Даже на разрешение прерывания interrupts(), проще и короче функции нет. Скорее всего, при вызове системных функций тоже происходит перегрузка переменных.
      Советую поступить так. В принципе, использовать циклы wile() для ожидания не хорошо. Я применил их в одном из начальных уроков, чтобы было проще объяснять алгоритм программы. В реальных программах я так никогда не делаю. Правильнее структуру программы строить по принципу из урока 17.
      if ( mode == 0 ) {
      // СИГНАЛИЗАЦИЯ ОТКЛЮЧЕНА
      }
      else if ( mode == 1 ) {
      // УСТАНОВКА НА СИГНАЛИЗАЦИЮ
      }
      else if ( mode == 2 ) {
      // ОХРАНА
      }
      else if ( mode == 3 ) {
      // БЛОКИРОВКА
      }
      else mode= 0;
      В этом случае цикл loop() не задерживается, всегда выполняется. В нем также могут находиться общие для любого режима действия.
      Рад, что Вы затронули эту тему. Если не согласны напишите.

      0
      • Добрый.
        Это не баг, небольшой пробел в знаниях.
        Вы неявно изменяете переменную, почитайте про квалификатор типов volatile.

        0
        • Спасибо! К своему стыду вынужден согласиться. Надо перед переменной поставить квалификатор volatile. Тогда компилятор отключит оптимизацию операций с этой переменной и будет ее проверять при каждом проходе цикла.
          В уроке 10 есть подробное описание volatile.

          0
          • Вам спасибо, я давно уже не программист, и вот ваши готовые куски кода очень кстати.

            0
  2. Эдуард,
    Да, я конечно соглашусь, что while (true) {} использовать не следует. Просто этот «баг» отнял довольно много времени, что неприятно. Но радует, что не у меня одного ))
    В общем, продолжаю в свободное время с удовольствием изучать ваши уроки, а также пытаюсь перенести принципы борьбы с дребезгом, описанные у вас здесь, на платформу ARM STM32. Еще раз большое спасибо, что описали эффективный алгоритм.

    0
  3. Здравствуйте, Эдуард.

    Начал изучать ваши уроки. Очень методичные, структурные, то что надо. В этой программе обнаружил ошибку. При выходе программы в режим тревога зуммер не прекращает работу после нажатия на секретную кнопку а также спустя 30 сек.
    В timerInterupt после if вставил такую строку:
    else digitalWrite(SIREN_PIN,LOW);
    Все стало в норме.

    0
    • Андрей, здравствуйте!
      В программе ошибки нет. Вы, наверное, использовали активную сирену. Т.е. напряжение на сирене есть — она верещит, напряжение сняли — молчит.
      А я использовал пассивный пьезоизлучатель, который пищит если на нем присутствует переменное напряжение частотой 0,5 — 2 кГц. Признак sirenOn не верен, значит сигнал на пьезоизлучателе не меняется, и он молчит.
      А в применении к активной сирене Вы абсолютно правы. Ее надо выключать. Ее надо и включать потенциалом. Тогда в прерывании ничего с сиреной делать не надо. Просто управлять ей из основного цикла.

      0
      • Да, Эдуард.

        Спасибо за ответ. Видимо так и есть у меня по рукой оказалась только активная сирена ТМВ12А05.
        Все остальное пока понятно, буду двигаться дальше по Вашим урокам.

        0
  4. Эдуард, доброго дня!

    Собрал по вашему примеру макетку, но вместо пищалки на 9 пин включен светодиод. Из режима тревоги диод этот нормально гаснет по нажатию кнопки secretbutton, но почему-то он не гаснет через 30 секунд. Почему так может быть?

    0
    • Здравствуйте! Кирилл, почитайте мой ответ на вопрос Андрея в комментариях к этому уроку. У Вас та же проблема. Я использовал пассивный пьезоизлучатель. Для выключения звука программа перестает формировать звуковую частоту на нем, т.е. перестает изменять уровень сигнала. Если сигнал на выводе остался при выключении звука в высоком уровне, то будет светится светодиод.

      0
  5. Здравствуйте, Эдуард.
    загрузил: «Скетч программы с расширением .ino можно загрузить по этой ссылке.» Выдало ошибку при проверке
    error: ‘timerInterupt’ was not declared in this scope
    Библиотеки подключил, чего то не хватает?

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

      0
  6. ЗДРАВСТВУЙТЕ ПОДСКАЖИТЕ КАК НАПИСАТЬ ЭТУ ЛОГИКУ.

    12 ПИН ЛОВ.
    ЕСЛИ НА 11 ПИНЕ (ЛОВ ИЛИ ХАЙ ТЕЧЕНИИ 2 МИНУТ) ТО
    12 ПИН ЛОВ
    ИНАЧИ
    12ПИН ХАЙ .

    0
  7. Здравствуйте, Эдуард! В данном уроке мне непонятен один момент:

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

    Почему на режим ТРЕВОГА программа переходит по признаку flagPress, а не flag.Click?

    0
    • Здравствуйте!
      Можно и так. Разницы особой нет. Где это возможно я предпочитаю анализировать текущее состояние, а не детектор фронта.

      0
      • Извините, Эдуард, я правда много думал, прежде, чем спросить! Но зачем нам тогда вообще переменная flagClick?

        0
        • flagPress показывает текущее состояние кнопки. Кнопку нажали flagPress стала равной true. Кнопку отжали flagPress стала равной false.

          flagClick показывает было ли нажатие, перешла ли кнопка из состояния отжата в состояние нажата. Эта переменная реагирует на перепад состояния кнопки. Нажали кнопку — flagClick стала равной true. А сброс этой переменной надо производить в программе после обработки события.

          Представьте, что вам надо на каждое нажатие кнопки инвертировать состояние светодиода. Как вы это будете делать с помощью flagPress.

          0
      • Извините, но разве разве нет разницы? При нажатии кнопки flagClick запоминается и сбрасывается только при следующем к нему обращении. Поэтому если в режиме Отключена нажать кнопку doorSens, а потом в режиме Охрана анализировать не flagPress, а FlagClick — то сигнализация сразу перейдет в режим Тревога. Поправьте меня если я не прав

        0
        • Здравствуйте!
          Признак FlagClick в режиме отключена запоминает, была ли дверь открыта. Разве это имеет значение? Нам важно открылась ли дверь в режиме охрана и неинтересна предыдущая история с дверью.

          0
          • я хотел сказать, что нужно использовать именно flagPress, а ни в ком случае не flagClick. Если использовать flagClick, то при переходе в режим Охрана сразу же попадем из него в режим Тревога (ну если в режиме Отключена был факт открытия двери)

            0
  8. Спасибо. Все круто.
    Но я бы заменил:
    <>
    на
    <>
    через 30 сек. не вырубается. Думаю нужно втащить здесь автомат состояний.

    0
    • Код не отобразился.
      Кароче, добавил команду на затыкание сирены и чуть изменил ваш код, добавив автомат состояний.
      Бесконечные циклы и ненавистные GOTO убрал.
      Не сочтите за нескромность.

      void loop() {

      switch (sost){
      case 0:{

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

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

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

      case 1:{

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

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

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

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

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

      //}
      }//case -1:{
      break;
      }
      }

      0
      • Ну и конечно для этого нужно объявить переменную, и в setup ей назначить значение по умолчанию.

        int sost = 0;//3 состояния
        // 0 = guard_off:
        // 1 = guard_on:
        // -1 = alarm: !!!

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

        sost = 0;// на всякий
        }

        Остальное не менял

        0
      • Поясните плиз как у вас работает такая конструкция:
        // светодиод мигает раз в секунду
        if ( ledTimeCount >= TIME_LED_PERIOD ) ledTimeCount= 0;
        if ( ledTimeCount = TIME_LED_ALARM ) {
        ledTimeCount= 0;
        digitalWrite(LED_PIN, ! digitalRead(LED_PIN));
        }
        Мне кажется светодиод будет мигать с частотой TIME_LED_ALARM , а не раз в секунду. Поправьте меня, если я не прав…

        0
  9. Здравствуйте, Эдуард! При загрузке скетча возникла одна проблема: программа не переходила на режим guard_off через заданный промежуток времени. Поставил перед счётчиком alarmTimeCount квалификатор volatile и всё заработало, может для кого то это будет полезно, но вопрос в другом: при использовании бесконечных циклов while нужно ли для всех переменных использовать volatile, чтобы не получить неожиданный результат?

    0
    • Здравствуйте! Это программа учебная, единственная в уроках программа с бесконечными циклами.
      Чтобы не было проблем, о которых вы пишите необходимо:
      — либо использовать с переменными, которые проверяются в цикле квалификатор volatille (урок 10);
      — либо в бесконечных циклах должна вызываться какая-либо системная функция, как в этом уроке;
      — либо каждый цикл программы должен завершаться выходом функции loop, как в та же сигнализация реализована в уроке 17.

      0
  10. Эдуард, здравствуйте!
    Стыдно сказать, встрял в самом начале. Написал начальный скетч в виде:
    #include
    #include

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

    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()
    {

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

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

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

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

    }
    Сирена при нажатии на кнопку молчит.

    Написал свой «проверочный» скетч для сирены в виде указанном ниже.
    При этом сирена (китайская пищалка из набора Arduino XPDZ) пищит. Но отключается только при нажатии на кнопку, если кнопку отпустить она продолжает пищать. То есть не происходит фиксация состояния. А иногда, примерно 1раз на 5 нажатий при нажатии на кнопку сирена не отключается, а частота звука растет примерно в 2 раза. Если не трудно, объясните в чем моя ошибка в первом случае и во втором?
    #include
    #define SECRET_BUTTON_PIN 11
    #define SIREN_PIN 9
    boolean sirenOn;
    Button secretButton(SECRET_BUTTON_PIN, 5);
    void setup()
    {
    pinMode(SECRET_BUTTON_PIN, INPUT);
    pinMode(SIREN_PIN, OUTPUT);
    }

    void loop()
    {
    secretButton.scanState();
    if(secretButton.flagClick == true)
    {
    secretButton.flagClick=false;
    sirenOn=!sirenOn;
    }
    if(sirenOn==true)
    {
    digitalWrite(SIREN_PIN, !digitalRead(SIREN_PIN));
    }
    else
    {
    digitalWrite(SIREN_PIN, LOW);
    }
    }

    0
    • Здравствуйте!
      Вы, наверное, использовали активную сирену. Т.е. напряжение на сирене есть — она верещит, напряжение сняли — молчит.
      А я использовал пассивный пьезоизлучатель, который пищит если на нем присутствует переменное напряжение частотой 0,5 — 2 кГц. Признак sirenOn не верен, значит сигнал на пьезоизлучателе не меняется, и он молчит.

      Активную сирену надо включать и выключать потенциалом из основного цикла.

      0
  11. Здравствуйте, Эдуард! При загрузке скетча возникла проблема: программа не переходила на режим guard_off через заданный промежуток времени, но в тоже время нормально переходила
    в него при нажатии кнопки открытия двери. Все заработало
    когда вместо: alarmTimeCount = 0; вписал:
    if(alarmTimeCount != 0) alarmTimeCount = 0;
    Потом в комментариях нашел, что это оптимизация компилятора и все можно было решить модификатором volatile. Но все равно не понятно почему в обработчике прерывания
    компилятор игнорировал инкремент переменной.
    Огромное спасибо вам за уроки, очень доходчиво и интересно.

    0
  12. Добрый день, Эдуард!
    Наткнулся на Ваши уроки пока искал реализацию контроллера Пельтье на Ардуино. Он мне нужен для моей научной работы, только в режиме термостата. Но решил ознакомиться с курсом с самого начала, тем более давно хотел разобраться с этой темой. Спасибо Вам за Ваш труд и обстоятельный подход!
    Касательно данного урока как и у многих возникли проблемы с отключением тревоги. Вроде разобрался, добавив квалификатор volatile и отключение «сирены» в явном виде. Отсюда первый вопрос. Вы писали, что не заметили удлинения программы при добавлении квалификатора volatile в код. Так может стоит по умолчанию добавлять его ко всем глобальным переменным? Чем это чревато? Или просто дурной стиль?
    Второй вопрос касается встроенного светодиода на пине 13. Хотя в явном виде он в этой программе не используется, он всё же некоторым образом реагирует на её выполнение. После первого включения «охраны» плавно разгорается, и затем горит постоянно, мигая на любое нажатие любой кнопки. Это нормально? С чем это связано? (вместо сирены, у меня ещё один светодиод).

    PS. На правах предложения, Вы не думали ввести на сайте ссылку для небольших пожертвований? Я понимаю, что это хобби, но считаю, что всякий труд должен оплачиваться. Тем более это же добровольно!

    0
    • Здравствуйте!
      Спасибо за приятные слова. Разные компиляторы по разному производят оптимизацию программы. В сложных случаях с классами иногда не помогают даже квалификаторы volatile.
      Я бы советовал писать программы на Ардуино так, чтобы каждый проход программы завершался через окончание цикла loop. В этом уроке единственная моя программа, которая не придерживается этого принципа. Я написал ее таким образом просто, чтобы было проще объяснять, но уже жалею об этом.
      На счет светодиода я не знаю. Возможно, брошенный в воздухе вход ловит разные помехи и утечки. От этого и светодиод меняет свою яркость.
      По поводу пожертвований — спасибо за предложение. Оно не первое. Не знаю, как это отразится ни имидже сайта. Подумаю.
      У меня все время возникают мысли создать сайт или раздел на этом сайте, в котором демонстративно вести разработки по заказу. Человек просит что-то разработать. Я поэтапно это делаю. Он проверяет, корректируем. В результате получается и разработка, и обучающий материал. А разрабатывать программы проще, чем писать уроки. Такая работа должна оплачиваться. Может быть за счет пожертвований.

      0
  13. Эдуард, подскажите плиз вот здесь:
    // светодиод мигает раз в секунду
    if ( ledTimeCount >= TIME_LED_PERIOD ) ledTimeCount= 0;
    if ( ledTimeCount < TIME_LED_ON ) digitalWrite(LED_PIN, HIGH);
    else digitalWrite(LED_PIN, LOW);

    получается, что выход LED_PIN "дергается" каждый проход цикла. Под словом "дергается" я понимаю что на него подается либо HIGH, либо LOW, ну что на него вообще что-то подается. Это разве хорошо? Не правильнее ли на него подать что нужно, а через заданное количество циклов снять это "что нужно"? Или я зря волнуюсь на счет то что выход задействуется при каждом проходе?

    0
  14. Спасибо огромное за ваши уроки, они заменяют любой кросвод и головоломки , отличное средство для разминки мозга да ещо и с пользой ещо раз огромное спасибо

    0
  15. Извините за дилетантский вопрос.
    Почему не происходит ошибка переполнения счетчика ledTimeCount, который объявлен как unsigned int. Если охранная система находится в отключенном режиме?

    0
      • Просто интересно что произойдет с переменной когда её значение превысит 4 294 967 295 т.е через приблизительно 100 дней, если сигнализация будет находится в режиме «выключена»?

        0
  16. Эдуард, для того что бы проверить как ведут себя переменные в контроллере в случае переполнения я написал небольшой sketch

    #include

    unsigned short int count;// тестовый счетчик переполнения
    void setup(){

    Serial.begin(9600);
    MsTimer2::set(1,goInterupt);//время запуска обработчика каждую милисекуду
    MsTimer2::start();
    }

    void loop(){
    Serial.println(count);/выводим значение счетчика через UART в ардуино монитор
    }

    void goInterupt(){/обработчик при каждом вызове увеличивает счетчик
    count++;
    }

    выяснил для себя, что при переполнении счетчик обнуляется.

    0
  17. Эдуард,здравствуйте!
    В первую очередь,хотелось бы вас поблагодарить за ваши уроки,они хоть и сложны для не подготовленного человека,но реально учат программированию,а не созданию безделушек под копир.Спасибо!
    Подскажите пожалуйста,как примерно рассчитать время выполнения операций.То есть,как я понимаю,программа данной сигнализации работает следующим образом:
    Идет цикл loop, каждые 2 мс он прерывается по таймеру ,и происходит выполнение условий из обработчика прерываний.В данном случае,он совершает 5 последовательных? действий:фильтрует сигналы кнопки и датчика открытой двери,проверяет не включена ли сирена и включает счетчики.
    Все это обработчик прерываний должен успеть сделать за какое то очень короткое время,так ведь?
    А что будет,если подпрограмма из обработчика прерываний занимает больше времени,чем указан цикл таймера?Предположим происходит преобразования в ацп?
    И как вообще желательно выбирать время для таймера?Чтоб он (обработчик) вызывался максимально часто или как?
    Опять таки,как я понял ,в обработчике должны выполняться максимально короткие события,а длинные и циклические лучше по програмному таймеру который считает в аппаратном.Блин…как сложно то.Простите за сумбур,я даже вопрос толком сформулировать затрудняюсь

    0
    • Здравствуйте! Спасибо за добрые слова.
      На языке C время выполнения операторов рассчитать невозможно. Можно определить опытным путем. На форуме есть информация на эту тему http://mypractic-forum.ru/viewtopic.php?t=17.
      В любом обработчике прерывания код должен выполняться максимально быстро, т.к. при этом блокируются другие прерывания и система нормально не работает.
      Если обработчик прерывания работает дольше периода прерывания, то нарушится цикл прерывания, будут пропускаться другие прерывания, не останется времени на выполнение основного цикла и т.п.
      Период прерывания надо выбирать максимально большим, но способным реализовать задачу. Чем реже вызываются прерывания, тем меньше процессор отвлекается от основной задачи. Но время прерывания должно вызываться достаточно часто, чтобы не пропустить нужное событие.

      0
      • Спасибо!
        То есть,в принципе,2мс-это вполне нормальный по меркам атмеги период прерывания?
        Прочитал публикацию на форуме.Очень интересно,а я все не мог понять,почему моя программа начинала страшно тупить,когда я в прерывании 2мс ,выводил на лсд информацию.А сейчас прочитал,что вывод строки это около 2,5 мс и очистка дисплея около того же,то есть в обработчике прерывания с периодом 2 мс такого делать категорически нельзя?И что делать в таком случае?выводить данные на лсд по програмному таймеру или увеличить период прерывания?
        И да,вопросов очень много на самом деле,стараюсь конечно разбираться сам,через другие сайты и книги,но все же где их лучше задавать,под записями или на форуме?

        0
        • А зачем с такой частотой выводить данные на индикатор?
          Данные на индикатор надо выводит в цикле loop(). В прерывании создать программный таймер, например, с периодом 300 мс. В прерывании формировать флаг с периодом 300 мс. В цикле loop() анализировать этот флаг. Если он стал активным, то сбрасывать его и выводить данные на индикатор.

          0
  18. Эдуард, здравствуйте
    А вот это:
    «а затем усложним, добавив GSM управление и оповещение, увеличим число датчиков, исполнительных устройств»
    Такой урок уже существует? или в проекте?

    0
    • Здравствуйте!
      Есть рабочий проект сигнализации в уроке 17. Он работает с настоящими охранными шлейфами, но без GSM и т.п.

      0
  19. очень доволен вашими уроками . они очень помогают . например из ваших уроков узнал что сложную программу чтобы не мучиться лучше писать блоками шаг за шагом компилируя программу после написания каждого блока . итак : сначала пишем весь остов программы в виде пустых незаполненных блоков , компилируем её пустую и если прошло . начинаем заполнять блоки и с каждым блоком всё время компилируем и проверяем и так доходим до конца . а если сразу написать большую программу то и никогда не найдёшь из за чего ошибка , бывает компилятор показывает ошибку где нибудь а на самом деле ошибка в написании совсем в другом месте .

    0
  20. вот маленькая программа уважаемые :
    class leds {
    int ledPin, ton ,toff; boolean states; unsigned long pret;
    public: leds(int pin, int on, int off) { ledPin=pin;
    pinMode(pin,OUTPUT); ton=on; toff=off; states=LOW; pret=0; }
    void upp(unsigned long curt) { if((states==HIGH)&&(curt-pret>=ton)) {
    states=LOW; pret=curt; digitalWrite(ledPin,states); }
    else if((states==LOW)&&(curt-pret>=toff)) { states=HIGH; pret=curt;
    digitalWrite(ledPin,states); } } };
    leds led1(12,30,200); leds led2(8,200,1500);
    void setup() { OCR0A=0xFA; TIMSK0 |=_BV(OCIE0A); }
    SIGNAL(TIMER0_COMPA_vect) { unsigned long curt=millis();
    led1.upp(curt); led2.upp(curt); }
    void loop() { }
    весело пищат 2 светодиода . один включен на 8 пин (контакт) .другой на 12 . смысл моргания не в развлечении , а в переходе к прошивке других процессоров STM и AVR . в этой программе нет delay() , хотя в наших простых проектах можно его вовсю применять . вместо него используется настройка регистров на прерывание . например вы включили кондиционер . каждый бит регистра играет роль таких включателей . это регистры фьюзов — включателей . есть ещё регистры флагов —лампочек сигнализирующих нам что то самое включилось . ton и on время включенного состояния. toff и off время выключенного . самое главное тут три действия
    OCR0A=0xFA; TIMSK0 |=_BV(OCIE0A);
    в регистр OCRA 0-го счётчика вписываем число FA ( равно250 ) командой OCR0A=0xFA;
    а вот уже в отдельном бите под названием OCIEA в регистре TIMSK опять у 0-го счётчика выставляем 1(единицу) . то есть приказ регистрам сравнивать показания счётчика тикающего без нас с числом записанным в регистр OCRA пишется командой TIMSK0 |=_BV(OCIE0A); могут писать и по другому вместо _BV можно писать 1<< . при совпадении сразу идет команда исполнять прерывание . естественно её надо расписать что выполнять процессору . вначале обязательно пишем ISR либо SIGNAL например
    SIGNAL(TIMER0_COMPA_vect) здесь надпись
    COMPA означает что прерывание идёт при компации(сравнении) . а то есть ещё надпись OVR это уже при переполнении . весьма поучительная програмка перед освоением прошивки других процессоров с помощью программаторов .

    0
  21. Хочу предложить свой вариант программного таймера:
    _
    unsigned long time_current;
    unsigned long time_fix1 = 0;
    unsigned long time_Delta;
    unsigned long Period1 = 100; //задаём период

    void setup() { }
    void loop() { time_current = millis(); // текущее значение таймера времени в миллисекундах
    time_Delta = time_current — time_fix1;
    if ( time_Delta >= Period1)
    {
    time_fix1 = time_current; // фиксируем новую точку отсчёта времени
    //код, который должен выполняться каждые 100 миллисекунд
    }
    }
    Период указывается прямо в миллисекундах. Код, написанный в loop может быть оформлен в отдельной функции. Таких функций может быть несколько, для каждой будет свой Period и своя точка для следующего отсчёта time_fix.
    Эдуард, как выглядит такой такой таймер с Вашей критической точки зрения? Заранее спасибо)

    0
    • Здравствуйте!
      Вариант, конечно, рабочий. Но вы все время вызываете функцию millis(). У нее возвращаемое значение long и она выполняется не очень быстро. И еще. Если у вас в цикле loop будут программные блоки, которые выполняются достаточно долго, то они будут задерживать опрос millis() и искажать время.

      0
  22. Здравствуйте, Эдуард. На мой взгляд здесь лучше использовать кнопки на размыкание, т.е кнопка активна при низком уровне сигнала. Потому как при обрыве кабеля/ неисправности датчика система будет неактивна и вы об этом даже не узнаете, пока не решите проверить. Либо необходимо устанавливать параллельно кнопке т.н. end of line resistor, было бы очень интересно взглянуть на вашу программную реализацию такой защиты. Ведь тут уже придется использовать вход МК как аналоговый. А так пользуясь случаем, огромное спасибо за уроки.

    0
    • Здравствуйте!
      Большинство существующих кнопок замкнуты в активном состоянии. С таким же успехом можно сказать, что вы не проверите нормально замкнутую кнопку в случае замыкания линии.

      0
  23. единственно в чём я уверен это самый лучший сайт по микропроцессорам . спасибо вам Эдуард за уроки . хоть я человек желчный и с дурным характером но искренне желаю вам долгой и счастливой жизни . именно так и никак не иначе . просто идеальный сайт . я вот год занимался ремонтом холодильников , стиральных машин и так далее включая ремонт дома и забыл всё . теперь надо всё вспомнить чтобы спроектировать микропроцессорную систему с применением RS485 , RFID считывателей и программ приложений для использования облака и всё повторяю и вспоминаю с помощью ваших уроков . огромное спасибо . лучшие уроки в мире .

    0
  24. unsigned int ledTimeCount; // счетчик времени для светодиода
    обратите внимание все !!! если перед названием любого счетчика не писать unsigned то есть просто написать int ledTimeCount; то тогда у вас счетчик досчитав до края в данном случае до int=32000 скинется процессором автоматически на минус -32000 и оттуда будет вылазить около двух часов и всё это время ваша программа будет висеть и не работать . так что для любых счётчиков обязательно впереди пишите unsigned

    0
  25. Здравствуйте!
    Мне кажется, более правильным алгоритм работы при срабатывании тревоги такой: после того как отключился звук надо не отключать сигнализацию а делать проверку датчика двери в бесконечном цикле. И если он восстановился по какой либо причине, сигнализация должна встать на охрану.

    0
  26. /* Урок 6 и 11. Выдержка времени. Принажати кнопки запускается таймер выдержки времени и через
    установленое время (#define LEDTimer 1500 //Выдержка времени для СИД1)
    загорается светодиод СИД.
    После повторного нажатия опять выдерка времени и СИД гаснет.

    Вот только у меня так не получается. А получилось, что после загрузки скетча в Ардуино
    кнопка не реагирует, а реагирует она после выдержки установленого времени (#define LEDTimer 1500 //Выдержка времени для СИД1)
    и СИД переключается одновремено с нажатием кнопки.
    Подскажите пожалуйста, в чем ошибка? Спасибо.*/

    #include //Библиотека таймера
    #include //Библиотека кнока

    #define LED_1_PIN 13 //СИД1 подключен к выводу 13
    #define BUTTON_1_PIN 2 //Кнопка подключена к выводу 2

    #define LEDTimer 1500 //Выдержка времени для СИД1
    boolean flagLEDTimer; //признак программного таймера
    int LEDCount; //счетчик таймера СИД1

    Button button1 (BUTTON_1_PIN, 15); //создание объекта-кнопка

    void setup(){

    pinMode (LED_1_PIN, OUTPUT); //определяем вывод СИД1 как выход
    MsTimer2::set(2, timerInterrupt);// задаем период прерывания по таймеру 2мс
    MsTimer2::start(); //разрешаем прерывание по таймеру
    }

    void loop(){

    //управление СИДом1
    if(button1.flagClick==true){ //был клик кнопки
    button1.flagClick=false; //сброс признака

    if(flagLEDTimer==true){
    flagLEDTimer=false; //сброс флага выдержки времени
    digitalWrite(LED_1_PIN,!digitalRead(LED_1_PIN)); //инверсия состояния СИДа
    }
    }
    }

    void timerInterrupt(){ //обработчик прерывания
    button1.scanState(); //вызов метода ожидания стабилбного состояния для кнопки

    LEDCount++; //+1 к счетчику тамера СИД1
    if(LEDCount>LEDTimer){
    LEDCount=0; //сброс счетчика
    flagLEDTimer=true; //установка флага выдержка времени СИД1
    }
    }

    0
    • Здравствуйте!
      У вас в обработчике прерывания идет постоянный отсчет времени независимо нажата кнопка, или нет.
      Может как-нибудь так. По кнопке запускается автомат, который реализован в прерывании.
      void loop(){
      //управление СИДом1
      if(button1.flagClick==true){ //был клик кнопки
      button1.flagClick=false; //сброс признака
      flagLEDTimer=true; //установка флага выдержка времени СИД1
      }
      }

      void timerInterrupt(){ //обработчик прерывания
      button1.scanState(); //вызов метода ожидания стабилбного состояния для кнопки

      if( flagLEDTimer == true ) {
      LEDCount++; //+1 к счетчику тамера СИД1
      if(LEDCount>LEDTimer){
      digitalWrite(LED_1_PIN,!digitalRead(LED_1_PIN)); //инверсия состояния СИДа
      flagLEDTimer=false;
      }
      else LEDCount=0;
      }
      }

      0
      • Эдуард здравствуйте!
        Спасибо за Ваш ответ-совет.
        Подправил свой скеч как Вы порекомендовали и….
        1. Загрузил скеч, работа программы изменилась но не так как задумано, а именно
        по прошествии выдержки времени (#define LEDTimer 1500 //Выдержка времени для СИД1)
        нажимаем кнопку и СИД тут же меняет свое состояние, следом нажимаем кнопку еще раз идет выдержка
        времени и СИД меняет свое состояние. Но, если следом не нажимаем кнопку, а ждем выдерку времени и нажать кнопку,
        то СИД меняет свое состояние тут же, т.е. повторяется как описано выше в данном абзатце.
        2. Полез в скеч поменял местами строки 39 } и 40 else LEDCount=0; загрузил скеч. СИД не светится на кнопку не реагирует.
        Вернул взад строки. Загрузил и опять СИД не светится, на кнопку не реагирует.
        Загрузил свой «корявый» скеч. СИД на кнопку реагирует. Загрузил скеч с Вашими рекомендациями в другую Ардуино (Нано) (для
        практических уроков пользуюсь Ардуино Уно), результат тот же СИД не светится на кнопку не реагирует.
        Такое «чудо» возможно?

        Прошу посетителей данного сайта помочь мне дельным советом.
        Спасибо.

        0
          • Спасибо Эдуард за ответ.
            не работает.

            0
          • Вот такой вариант не работает?
            void loop(){
            //управление СИДом1
            if(button1.flagClick==true){ //был клик кнопки
            button1.flagClick=false; //сброс признака
            flagLEDTimer=true; //установка флага выдержка времени СИД1
            }
            }

            void timerInterrupt(){ //обработчик прерывания
            button1.scanState(); //вызов метода ожидания стабилбного состояния для кнопки

            if( flagLEDTimer == true ) {
            LEDCount++; //+1 к счетчику тамера СИД1
            if(LEDCount>LEDTimer){
            digitalWrite(LED_1_PIN,!digitalRead(LED_1_PIN)); //инверсия состояния СИДа
            flagLEDTimer=false;
            }
            }
            else LEDCount=0;
            }

            0
    • Добавьте volatile при описании флага, который связывает обработчик прерывания с основным циклом. Я не обратил внимание на описание переменных.
      volatile boolean flagLEDTimer; //признак программного таймера
      Так все работает.

      0
      • Добрый день Эдуард!

        Последнее Ваше предложение установить квалификатор volatire не помог.
        Но, благодаря Вашей подсказке я решил задачу используя «счетчик таймера
        конралируется в асинхроном цикле». Квалификатор volatire не используется.

        #include //Библиотека таймера
        #include //Библиотека кнока

        #define LED_1_PIN 9 //СИД1 подключен к выводу 9
        #define BUTTON_1_PIN 2 //Кнопка подключена к выводу 2

        #define LEDTimer 1000 //Выдержка времени для СИД
        boolean flagLEDTimer; //признак программного таймера
        int LEDCount; //счетчик таймера СИД2

        Button button1 (BUTTON_1_PIN, 15); //создание объекта-кнопка

        void setup(){

        pinMode (LED_1_PIN, OUTPUT); //определяем вывод СИД1 как выход
        MsTimer2::set(2, timerInterrupt);// задаем период прерывания по таймеру 2мс
        MsTimer2::start(); //разрешаем прерывание по таймеру
        }

        void loop(){

        //управление СИДом
        if(button1.flagClick==true){ //был клик кнопки
        button1.flagClick=false; //сброс признака
        flagLEDTimer=true;
        }

        //выдержка времени для СИДа
        if(flagLEDTimer==true){
        if (LEDCount>LEDTimer){
        digitalWrite(LED_1_PIN, !digitalRead(LED_1_PIN));
        flagLEDTimer=false;
        }
        }
        else LEDCount=0;
        }

        void timerInterrupt(){ //обработчик прерывания

        button1.scanState(); //вызов метода ожидания стабильного состояния для кнопки

        LEDCount++;//+1 к счетчику
        }

        0
        • Так я проверил последний вариант на реальной плате. Все работает.
          #include
          #include

          • Эдуард здравствуйте!
            Заработала.
            Спасибо.
            Строка «Serial.begin(9600);» зачем?
            Да, без нее не работает.

            0
          • Должно без нее работать. Я забыл ее удалить, когда выводил в терминал состояние флага.

            0

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Нажимая кнопку "Отправить" Вы даёте свое согласие на обработку введенной персональной информации в соответствии с Федеральным Законом №152-ФЗ от 27.07.2006 "О персональных данных".